Vous êtes sur la page 1sur 552

Création graphique de la couverture : Hokus Pokus Créations

© Dunod, 2019
11 rue Paul Bert, 92240 Malakoff
www.dunod.com

ISBN : 97-8-21-007920-3

Ce document numérique a été réalisé par PCA


Table

Couverture

Page de titre

Page de Copyright

PREMIÈRE PARTIE - Les bases de JavaScript

1 Réflexions autour du JavaScript

1.1 JavaScript, c'est quoi ?

1.2 brève histoire du JavaScript

1.3 le standard ECMAScript

1.4 rappel sur le principe client/serveur

1.5 les possibilités du JavaScript

1.6 limites et sécurités du JavaScript

1.7 impact sur les performances et le référencement

1.8 l'image du JavaScript et son avenir

1.9 place du JavaScript dans un projet web

2 Hello(s) World(s)

2.1 les exemples de Hello World

2.2 la console des navigateurs

2.3 l'extension Web Developer


2.4 l'outil Google PageSpeed

2.5 JavaScript non activé

2.6 le bon éditeur de texte

2.7 programmer et partager du JS en ligne

2.8 mettre un site en ligne

2.9 les bonnes pratiques

3 Structure d'un script

3.1 intégrer du JavaScript

3.2 les variables

3.3 les blocs d'instructions

3.4 les tests conditionnels

3.5 les conditions

3.6 les boucles

3.7 les fonctions

4 Les chaînes de caractères en JavaScript

4.1 déclaration d'une chaîne de caractères

4.2 concaténation de chaînes

4.3 l'objet string

4.4 le traitement des caractères spéciaux

4.5 exécuter une chaîne JavaScript


4.6 manipulations avancées avec les expressions régulières

5 Les mathématiques

5.1 les conversions de types de données

5.2 l'objet Math

5.3 les nombres aléatoires

5.4 l'affichage formaté de nombres

6 Manipulation de dates

6.1 l'objet Date

6.2 les minuteries

6.3 l'affichage formaté de dates

7 Les tableaux

7.1 utilité des tableaux

7.2 l'objet Array

7.3 les tableaux spéciaux

7.4 la manipulation de tableaux

8 Programmation objet et JSON

8.1 les principes de la programmation objet

8.2 créer des objets JavaScript

8.3 le format JSON

8.4 résumé de la POO JavaScript


9 Réintroduction au JavaScript

9.1 les évolutions de structure du langage

9.2 les évolutions sur les objets

9.3 comprendre un appel JavaScript

10 L'objet maître window

10.1 l'objet window

10.2 manipulation des pop-up

10.3 manipulation des frames

DEUXIÈME PARTIE - L'interactivité

11 La programmation événementielle

11.1 programmation événementielle

11.2 le gestionnaire d'événements en détail

12 Manipuler le document

12.1 le HTML dynamique

12.2 trouver les éléments du document

12.3 manipuler les éléments

12.4 créer de nouveaux éléments

12.5 le drag and drop

12.6 la sélection de texte

13 Les formulaires
13.1 l'objet Form

13.2 les éléments de formulaires

13.3 les contrôles de saisie

13.4 les données côté serveur

14 Les appels AJAX

14.1 appels asynchrones

14.2 le traitement côté serveur

14.3 les contraintes liées à AJAX

15 La gestion des erreurs

15.1 la détection d'une erreur

15.2 l'objet Error

16 Les cookies et l'objet Storage

16.1 les cookies

16.2 le stockage de données locales

16.3 les différences entre cookie et l'objet storage

17 La géolocalisation

17.1 confidentialité et sécurité

17.2 obtenir la position actuelle

17.3 obtenir les positions régulièrement

17.4 les scripts de cartographie


18 Les notifications

18.1 le principe des notifications

18.2 les notifications en JavaScript

19 Le dessin et les canvas

19.1 l'élément Canvas

19.2 interactivités et animations

TROISIÈME PARTIE - Pour aller encore plus loin avec JavaScript

20 Monétisation et publicité

20.1 la monétisation

20.2 la publicité

20.3 les bloqueurs de publicité

21 Introduction à Node.js

21.1 installation de Node.js

21.2 utilisation de Node.js en serveur web

21.3 utilisation de npm

21.4 un serveur web Node.js prêt à l'emploi

21.5 node.js et les WebSockets

22 Les langages dérivés du JavaScript

22.1 typeScript

22.2 babel
22.3 JSX

23 Bibliothèques et frameworks

23.1 frameworks

23.2 jQuery

23.3 prototype.js

23.4 react

23.5 angular

23.6 vanilla.js

23.7 quelques biliothèques JavaScript utiles

24 Les Web Workers

24.1 principes des Web Workers

24.2 un cas réel de Web Worker

25 Créer une extension de navigateur

25.1 JavaScript dans un lien de la barre des favoris

25.2 créer une extension sur Google Chrome

26 Créer des applications

26.1 création d'applications de bureau

26.2 création d'applications mobiles

Annexe CSS

Utilisation de FontAwesome
La pseudo-classe :hover

Les pseudo-classes :first-child et :last-child

Les pseudo-éléments ::before et ::after

les boîtes fléchées avec CSS

les animations CSS

les transformations CSS

Index
A. Cazes, J. Delacroix
288 pages
2016

F. Lemainque
128 pages
2018

P.-Y. Cloux, T. Garlot, J. Kohler


304 pages
2019

C. Aubry
384 pages
2018
AVANT-PROPOS

◆ Pourquoi un nouveau livre sur le JavaScript ?

Dix ans après mon premier ouvrage sur le JavaScript, paru chez
Micro-Application, une totale réécriture était nécessaire.
J’ai écrit ce livre avec une vision plus large que le simple
apprentissage du JavaScript. J’ai voulu apporter au lecteur des
principes qui seront utiles dans la programmation sur tous les
langages. J’ai aussi ajouté des conseils sur les facteurs de succès
d’un projet web. Après une expérience de plus de douze ans en tant
que créateur et éditeur d’un site web leader dans sa thématique, j’ai
voulu partager mes acquis en abordant les optimisations de
performances, d’ergonomie et de référencement, jusqu’au modèle
économique.

◆ À qui s’adresse ce livre ?

Ce livre s’adresse à tous les développeurs web, qu’ils soient


débutants ou avancés, en phase de formation à l’école, en auto-
apprentissage par passion de la programmation ou en poste dans
une entreprise du net.
Des connaissances préalables sur les langages HTML et CSS sont
souhaitables.
Un lecteur qui découvre la programmation aura intérêt à lire cet
ouvrage dans l’ordre et en intégralité, presque à l’image d’un roman.
L’enchaînement des chapitres est progressif et ponctué de
remarques et de conseils adaptés à la vraie vie d’un projet web.
Un développeur plus à l’aise avec le langage pourra se reporter à
l’index, riche et varié, pour trouver directement les fonctionnalités du
langage ou des notions plus générales qu’il souhaite approfondir ou
découvrir.
Dans tous les cas, les deux premiers chapitres seront instructifs pour
tous les lecteurs, avec un point de vue sur la place du JavaScript
dans un projet et des rappels sur les bons outils et les bonnes
pratiques de programmation.
Les lecteurs experts observeront sans doute quelques simplifications
par rapport à la terminologie officielle ; elles sont destinées à rendre
les notions plus facilement compréhensibles. La méthode log() de
l’objet console sera par exemple présentée aussi comme une
fonction.

◆ Renvois vers le site toutjavascript.com

Afin de rendre ce livre moins volumineux, et donc moins cher, j’ai


adopté une écriture assez condensée, avec les captures d’écrans
strictement nécessaires et des extraits de scripts limités à la partie
essentielle.
Des renvois vers les exemples complets sont proposés via des liens
raccourcis au format tjs.ovh/nomScript. Ces renvois, véritables
compléments interactifs à ce guide, affichent :
le rendu de l’exécution du script ;
un émulateur de la console du navigateur ;
le code source complet de l’exemple avec une coloration
syntaxique, des commentaires et des liens vers les fiches de la
référence JS de mon site toutjavascript.com.

◆ Erratum

La page http://www.toutjavascript.com/livre/erratum.php contient les


éventuels errata relevés par les lecteurs. N’hésitez pas à y déposer
vos remarques pour améliorer et compléter les informations
publiées.

◆ Remerciements

Je tiens à remercier les lecteurs de mon premier livre, sans qui cette
version entièrement nouvelle n’aurait pas existé.
Je remercie également mon éditeur, Jean-Luc Blanc, chez Dunod,
pour son professionnalisme. Il a réussi à me comprendre et à me
motiver.
Sans la patience et les encouragements de Stephy, je n’aurai jamais
été au bout de ce long travail.
Merci à ma mère pour la relecture attentive du manuscrit.
Mon chien, qui a supporté les longs mois d’écriture des deux livres,
ne verra hélas pas l’aboutissement de cette version.
Je profite également de cet espace un peu plus personnel pour
exprimer ma gratitude en tant qu’entrepreneur du web à trois
sociétés incontournables. Elles ont énormément participé à la
démocratisation d’Internet et donc à la réussite d’entreprises telles
que la mienne :
Google, pour avoir rendu l’accès à l’information bien plus facile
aux utilisateurs ;
Free et son patron emblématique, Xavier Niel, pour avoir créé
les forfaits illimités ADSL et Mobile à tarifs imbattables ;
OVH et son créateur, Octave Klaba, pour avoir offert des
solutions d’hébergement fiables et très abordables à tous les
diffuseurs de contenus.
PREMIÈRE PARTIE

Les bases de JavaScript

Dans tous les langages et tous les domaines de compétence,


des bases saines sont nécessaires pour progresser de
manière efficace et durable.
Cette première partie ne sera pas pour autant rébarbative.
Elle permettra une bonne compréhension de l’usage du
JavaScript, de ses possibilités et de ses limites.
Tous les chapitres expliquent les notions en tenant en compte
de la norme ECMAScript 6. Le dernier chapitre de cette
première partie regroupe toutes les nouveautés
d’ECMAScript 6 et sera particulièrement utile aux lecteurs
ayant besoin d’une mise à jour sur les nouveautés du
langage.
1

Réflexions
autour du JavaScript

Objectif
Dans ce chapitre, nous allons définir les grands principes du
JavaScript dans l’appel et le rendu d’une page web et, au
travers de ses 25 ans d’histoire, ses possibilités, ses limites et
son avenir.

_ 1.1 JAVASCRIPT, C’EST QUOI ?


◆ 25 ans d’histoire et encore de l’avenir

Le JavaScript est un langage de programmation sous forme de


scripts, c’est-à-dire sans phase de compilation du code source vers
un langage de plus bas niveau. L’exécution du script se fait
directement par le navigateur à partir du code écrit par le
développeur.
Le JavaScript est également un langage événementiel, à l’écoute
de tous les événements qui peuvent se produire, comme un clic ou
une minuterie. À chaque événement intercepté, une action peut être
déclenchée.
Le JavaScript est aussi un langage orienté objet. Il utilise la
notation pointée pour accéder aux propriétés et fonctionnalités.
Chaque élément de la page est un objet de l’arborescence de l’objet
maître window.
Le JavaScript est principalement employé dans les pages web pour
les rendre interactives et dynamiques. Il est maintenant tellement
puissant et compatible avec l’ensemble des navigateurs modernes
que de véritables applications au format web sont utilisées par des
millions d’internautes et remplacent déjà les traditionnels logiciels
sous forme d’exécutables. Les interfaces en ligne des messageries,
des cartographies, des éditeurs de texte ou même de tableurs sont
réalisées en JavaScript et n’ont rien à envier à leurs homologues
exécutables.
Le JavaScript est également de plus en plus utilisé comme
plateforme serveur pour remplacer à la fois le serveur web (comme
Apache, le plus utilisé à ce jour) et le langage de programmation
serveur (comme PHP). Nous aborderons cette approche dans la
troisième partie de ce guide avec l’introduction à Node.js.
Par convention et par son historique, si aucune précision n’est
apportée, le JavaScript dont nous parlons dans ce livre se rapporte
au script exécuté dans une page HTML.
Le JavaScript, fort logiquement abrégé JS, n’a pas de logo officiel.
Le logo qui a tendance à s’imposer actuellement est un simple carré
jaune, avec les lettres noires JS alignées en bas à droite.

Logo du langage JavaScript

_ 1.2 BRÈVE HISTOIRE DU JAVASCRIPT


Brendan Eich, ingénieur informaticien américain, a inventé le
JavaScript en 1995 chez Netscape et l’a intégré dans le navigateur
du même nom. Brendan Eich participera également à la création de
la fondation Mozilla.
À l’origine, le nom du langage était LiveScript. Mais Netscape
profite à cette époque de la notoriété grandissante de Java pour le
renommer en JavaScript. Cet artifice marketing particulièrement
malvenu entraîne des confusions sur l’utilité du langage et, près de
25 ans plus tard, porte encore préjudice au JavaScript et à tout son
écosystème.
Devant le succès du langage et du navigateur Netscape, Microsoft
développe de son côté le langage de script JScript et l’intègre en
1996 dans Internet Explorer 3.

Un navigateur est un logiciel de navigation sur l’ensemble des


sites Internet du web. Nous emploierons également dans ce guide
le terme original anglais de browser.

_ 1.3 LE STANDARD ECMASCRIPT


Le langage JavaScript a été standardisé à partir de 1997 par
l’organisation ECMA International sous le nom ECMAScript avec le
standard numéro ECMA-262. Aujourd’hui, JavaScript, JS,
ECMAScript et son abréviation ES désignent le même langage.
Depuis la première version du standard, le langage a naturellement
évolué pour prendre en compte les nouvelles technologies
matérielles, les nouvelles propositions et les nouveaux besoins des
membres de l’organisation, et pour corriger les bugs. Les différentes
versions de standards ECMAScript ont ensuite été adoptées et
implémentées par les éditeurs de navigateurs, plus ou moins
rapidement, et avec plus ou moins de bonne volonté. N’oublions pas
que les membres de l’organisation, comme Google, Apple ou
Microsoft, sont également de féroces concurrents au quotidien.
Voici un tableau récapitulatif des différentes éditions d’ECMAScript :
Nom de Année de
Événements notables
l’édition publication
1 1997 Premier standard
2 1998 Corrections
Améliorations (String, Error,
3 1999
Number…)
Aucun accord entre les
4 Abandonnée
membres
Support natif du format
5 2009
JSON
Nombreuses évolutions qui
6 ou 2015 2015 font du JavaScript un vrai
langage moderne
7 ou 2016 2016 Itération annuelle
8 ou 2017 2018 Itération annuelle
Ce tableau montre les difficultés rencontrées par les membres pour
tomber d’accord. Il a fallu dix ans entre la publication de la version
ES3 et celle de la version ES5, avec entre-temps l’abandon notable
de ES4.
Dix ans représentent une éternité sur le web. Des technologies
apparaissent, des géants naissent et des champions d’hier meurent
en moins d’une décennie.
1999 peut être considérée comme l’année de la véritable
démocratisation de l’accès Internet en France avec la création de la
société Free et sa première offre d’accès par modem RTC.
En 2009 est sorti l’iPhone 3GS. Il est déjà la troisième itération du
premier iPhone, sorti en 2007.
En dix ans, nous sommes passés d’un Internet lent, facturé à la
minute, visible sur des moniteurs cathodiques, au smartphone tactile
en 3G, sans qu’aucune norme du langage JavaScript ne soit
publiée. Il n’y a rien d’étonnant à ce que chaque éditeur ait pris sans
attendre des décisions pour adapter son navigateur aux évolutions
technologiques. Chaque navigateur a donc développé des
fonctionnalités et des rendus qui lui étaient propres dans un esprit de
conquête de parts de marché.
Cette absence de concertation a été un véritable calvaire pour les
développeurs web, avec de nombreuses versions de leur site à
maintenir et avec de nombreuses exceptions et incompatibilités.
Il a fallu encore six ans pour arriver à la version ES6, appelée aussi
ES2015, et parvenir à une spécification du langage complète et
moderne, qui peut être considérée comme entièrement disponible
dans la dernière version à jour de l’ensemble des navigateurs du
marché.
La version ES6 a défini de nouvelles formes de notations qui
nécessitent un chapitre entier de remise à niveau pour les
développeurs web habitués à la syntaxe plus classique (voir ici).
Pour éviter de nouveaux délais aussi longs et des changements
aussi radicaux après chaque publication, il a été décidé que les
éditions suivantes seraient moins ambitieuses et adopteraient le
principe d’itérations régulières et annuelles, plus faciles à définir et à
implémenter.

_ 1. 4 RAPPEL SUR LE PRINCIPE


CLIENT/SERVEUR
Pour bien comprendre le rôle et l’emploi du JavaScript, il est
indispensable de maîtriser parfaitement le principe de
fonctionnement en client/serveur d’une page web visualisée par un
internaute dans son navigateur. L’analogie avec le serveur et son
client dans le cadre d’un café est assez juste. Le client commande
au serveur un expresso serré. Le serveur prépare la tasse, la pose
sur son plateau avec une sucrette, une cuillère et un verre d’eau. Et
enfin, il dépose l’ensemble de la commande sur la table du client.
Dans le cas d’une page web, le client est le navigateur de
l’internaute. Le serveur est l’ensemble de l’architecture informatique
hébergée dans un ou plusieurs datacenters de la société éditrice du
site Internet. Le navigateur demande par exemple au serveur de son
réseau social favori de lui afficher sa page d’accueil. Le serveur
construit la page adaptée à son client, avec les actualités de ses
amis et ses derniers messages. Le serveur web envoie le code
HTML de la page préparée, les images et les autres ressources
associées vers le navigateur qui l’interprète et l’affiche à l’utilisateur.
Bien sûr, la comparaison avec une commande de boisson est
simpliste. La communication entre le navigateur et le serveur est
bien plus codifiée et nécessite l’utilisation stricte du protocole HTTP
(HyperText Transfer Protocol) indiqué devant les adresses dans la
barre de navigation du browser.
Voici un schéma des communications entre le navigateur et le
serveur lors de la demande de la page d’accueil d’un site :

Schéma de communication client/serveur

L’affichage d’une page se réalise en sept grandes étapes :


1. Le navigateur envoie une requête HTTP au serveur de google.fr.
2. Le serveur fabrique le code HTML de la page demandée et le
compresse.
3. Le serveur envoie le code HTML dans une réponse à la requête
HTTP du navigateur.
4. Le navigateur reçoit en réponse le code HTML et le décompresse.
5. Le navigateur interprète le code HTML ligne par ligne et
commence l’affichage de la page.
6. Le navigateur envoie de nouvelles requêtes vers le serveur pour
chacun des éléments à charger (images, styles, scripts).
7. Le rendu de la page est terminé.
Tous les objets connectés à Internet communiquent entre eux grâce
à leur adresse IP, une suite de quatre nombres variant de 0 à 255.
Les humains sont plus à l’aise avec les mots qu’avec les chiffres :
c’est le protocole DNS (Domain Name System) qui traduit les noms
de domaine en leur adresse IP. Le navigateur fait ainsi appel aux
serveurs DNS du fournisseur d’accès pour trouver l’adresse IP du
serveur de la page demandée.
La compression GZIP des données transportées sur le réseau par le
protocole HTTP optimise les temps de rendu, car le temps gagné
pour le transfert sur le réseau est largement supérieur aux temps de
traitements de compression sur le serveur et de décompression par
le navigateur.
Le protocole HTTP/2 est une nouvelle version majeure encore peu
utilisée du protocole HTTP destinée à accélérer les transferts sur le
réseau. Une seule requête peut maintenant contenir plusieurs
ressources à la fois (images, vidéos, fichiers JavaScript…),
réduisant fortement la latence et les temps de rendu de la page
complète.
Nous verrons à plusieurs reprises dans ce livre les implications
concrètes sur le développement web des différentes phases de
communication entre les côtés client et serveur.

_ 1.5 LES POSSIBILITÉS DU JAVASCRIPT


Le JavaScript utilisé côté client et exécuté par le navigateur dans
une page web est parfaitement adapté pour les traitements
suivants :
Assistance de saisie des formulaires
• Contrôle et validation de saisie
• Affichage de messages d’aide à la saisie
• Éditeur de texte enrichi
Sauvegarde de données sur le poste local
• Via les cookies
• Via une zone disque dédiée
Gestion des nombres, dates et heures
• Calculs mathématiques
• Calculs sur les dates
• Affichage dans le format du pays de l’utilisateur
Animations graphiques
• Menus et éléments de navigation
• Animations esthétiques
• Défilements, diaporamas, zooms
• Graphiques animés et interactifs
• Cartographies
• Jeux
Appels asynchrones vers le serveur pour actualiser la page
• Cours de Bourse
• Chat de messagerie
• Alerte en direct
• Sauvegarde de saisie automatique en temps réel
• Affichage de services externes
• Liste des posts d’un réseau social
• Mesure d’audience
• Campagne publicitaire
Il n’existe aucun autre langage intégré aux navigateurs qui soit aussi
puissant et universellement reconnu que le JavaScript pour tous ces
traitements. Si le programmeur logiciel a l’habitude d’avoir une vaste
gamme de langages à choisir selon ses habitudes ou ses besoins,
en revanche, le JavaScript n’a pas d’alternative !
Le JavaScript utilisé côté serveur n’a pas du tout la même finalité. Il
est capable de remplir l’ensemble des fonctionnalités d’un langage
serveur classique comme PHP, ASP, Python, C, Java… De plus en
plus de sites sont développés en JavaScript côté serveur : forums,
outils de statistiques, interfaces d’administration…

_ 1.6 LIMITES ET SÉCURITÉS


DU JAVASCRIPT
Le JavaScript, et les navigateurs qui l’exécutent, ont évolué avec les
menaces apparaissant progressivement sur le web. Au début,
certains développeurs JavaScript abusaient des pop-up, qui ont été
rapidement bloqués par les navigateurs. Des scripts perturbateurs
tentaient de faire planter le navigateur en lançant des traitements
lourds et infinis. Récemment, des scripts parasites de création de
crypto-monnaies (minage) sont apparus pour voler de la puissance
processeur aux utilisateurs. À chaque fois, les éditeurs de
navigateurs ont intégré des parades, en anticipant la future
innovation malveillante et en préparant la parade.
La sécurité sur le net est devenue un enjeu vital. Via un programme
de Bug Bounty, tous les acteurs du web offrent des récompenses,
allant jusqu’à plusieurs dizaines de milliers de dollars, aux
chercheurs qui découvrent des failles de sécurité dans leurs
logiciels.
Le JavaScript, étant exécuté localement à l’intérieur d’un navigateur
qui sécurise son usage, a de nombreuses limites fonctionnelles.
Il n’est pas possible, sans une action volontaire supplémentaire
de l’utilisateur :
à un site d’accéder au disque dur de l’utilisateur (hormis une
zone strictement réservée au stockage de données site par
site) ;
à un site d’accéder à la zone de stockage ou aux cookies d’un
autre site ;
à un site de lire l’historique de navigation, les mots de passe ou
les favoris de l’utilisateur ;
à un virus ou un logiciel espion de s’installer ou d’effacer des
fichiers ;
à un script de faire planter le navigateur, et encore moins le
poste complet ;
à un script d’abîmer ou de détruire le matériel de l’utilisateur.
Il faut bien prendre conscience que la sécurité du JavaScript et des
navigateurs est garantie uniquement en l’absence d’action volontaire
de l’utilisateur. Un script ne peut pas installer un logiciel sans
l’affichage d’un message d’avertissement, prévenant de son
intention. Mais si le visiteur clique et confirme sans lire le message,
le logiciel s’installera, avec des conséquences potentiellement
dramatiques, car un logiciel installé possède tous les droits d’accès
à l’ensemble de l’appareil et du réseau. Le plus souvent, l’objectif
des créateurs de scripts et logiciels malveillants est de gagner de
l’argent. Par exemple, les ransomwares, littéralement « logiciels de
rançon », chiffrent le contenu du disque dur et interdisent l’accès aux
documents en espérant que l’utilisateur paiera une rançon en
échange d’une clé de déblocage.
De la même manière, un script ne peut pas récupérer uniquement
par lui-même les informations d’identification sur le site de votre
banque en ligne. Mais si un script utilise la technique de phishing,
ou « hameçonnage », et tente de se faire passer pour le site d’une
banque, via des redirections invisibles et une interface identique, la
sécurité native est contournée. C’est à l’utilisateur d’être attentif sur
ce qu’il fait et sur quel site il le fait.
Le JavaScript ne permet pas non plus d’obtenir l’adresse IP de
l’utilisateur.
Les limites imposées par le langage et sa bonne sécurité font partie
des raisons qui ont permis son universalité qui le rendent
incontournable.
Le JavaScript est un langage de script intégré au code HTML de la
page web. Il n’est donc pas possible de cacher son code source au
monde extérieur. Le navigateur en a besoin pour l’exécuter, comme
il a besoin du code HTML pour interpréter et afficher la page. Des
systèmes d’obfuscation, littéralement « obscurcissement », existent
pour rendre le code difficilement compréhensible par un humain, en
le compactant au maximum et en changeant entre autres le nom des
fonctions et des variables. Mais l’essence du code est toujours
présente et un travail de reverse engineering, opération inverse à
l’obfuscation, est relativement simple à réaliser sur un script.
Notez que le code JavaScript côté serveur n’est pas du tout visible
par le monde extérieur. Il s’exécute sur le serveur et retourne du
code HTML ou des données, le plus souvent au format JSON.

_ 1.7 IMPACT SUR LES PERFORMANCES


ET LE RÉFÉRENCEMENT
Il peut paraître étrange d’aborder dès le chapitre d’introduction des
notions aussi éloignées de la programmation JavaScript que le taux
de rebond ou le référencement. Le succès d’un site web se
construit à toutes les étapes de sa vie, depuis sa conception, sa
réalisation et jusqu’à son exploitation.
Le JavaScript a un impact très important sur le temps de
chargement et de rendu de la page dans le navigateur. Pour un
résultat final totalement identique, des optimisations simples
permettent de gagner jusqu’à une seconde, voire davantage, pour
un site particulièrement mal configuré au départ.
Comme le JavaScript joue un rôle central dans l’ergonomie d’un
service web, le développeur doit l’utiliser de manière pertinente,
dans le but de rendre service aux utilisateurs. Un site peu intuitif ou
trop lent fait fuir ses visiteurs dès les premières secondes. La part de
visiteurs qui ne consulte qu’une page en moins de quelques
secondes est appelée taux de rebond.
Les temps de chargement et le taux de rebond sont des indicateurs
officiellement reconnus des moteurs de recherche et influencent le
référencement d’une page. Le moteur de recherche s’appuie sur ces
critères, parmi des centaines d’autres, pour privilégier les sites
apportant une réponse satisfaisante aux utilisateurs. N’oublions pas
que les moteurs de recherche se livrent aussi entre eux une
concurrence sévère sur un marché mondial à plusieurs dizaines de
milliards de dollars annuels. Leur objectif est simplement de donner
les meilleurs résultats pour satisfaire et fidéliser leurs utilisateurs.
Dans le présent paragraphe, il s’agit simplement d’alerter sur les
enjeux et les impacts d’un simple bout de JavaScript, non seulement
pour la partie code source, mais aussi sur son résultat. Nous
aborderons dans le chapitre 2 les bonnes pratiques, les outils
disponibles et la vision d’ensemble à ne pas perdre de vue dans le
cadre d’un projet web.

_ 1.8 L’IMAGE DU JAVASCRIPT


ET SON AVENIR
Il faut bien l’admettre, le JavaScript, et ses développeurs, ont une
bien piètre image de marque. Le grand public le confond
immanquablement avec Java et lui associe son cortège de failles de
sécurité et de mises à jour laborieuses. Les informaticiens, au sens
large, voient en lui un langage ancien, sans grand intérêt et
dépassé, tout juste capable de déclencher des animations inutiles.
Les programmeurs lui reprochent sa syntaxe brouillonne et laxiste.
Le milieu du web imagine qu’il suffit de le substituer à une
bibliothèque de scripts ou à un framework pour répondre à tous les
besoins en une ligne de code.
Bref, le JavaScript est totalement incompris, même par des
développeurs web qui le connaissent et se souviennent des
incompatibilités entre navigateurs, des tests interminables et des
rustines répugnantes à mettre en place.
Depuis ECMAScript 6, les navigateurs se comportent bien sur
l’ensemble des fonctionnalités de base. Le langage a beaucoup
progressé, sans corriger toutefois certaines incohérences issues de
son histoire chaotique. Mais il est maintenant possible de créer des
projets incroyables. Le chapitre 9 est dédié à une « révision » (au
sens de nouvelle vision) du JavaScript, alias ES6.
Le JavaScript, 25 ans après sa création, est enfin standardisé et
régulièrement maintenu par une organisation puissante. Il est utilisé,
sans qu’ils le sachent, par 4 milliards d’internautes dans le monde,
selon des estimations de 20181. Il est au cœur du modèle
économique publicitaire du web. En l’absence de langage alternatif,
il est donc incontournable. Difficile d’imaginer, sans une révolution
complète de l’accès au web, que le JavaScript ne poursuive pas sa
croissance aux niveaux usage, compatibilité et fonctionnalités.

_ 1.9 PLACE DU JAVASCRIPT DANS


UN PROJET WEB
Une belle équipe de ninjas du JavaScript est probablement en place
pour créer et maintenir l’interface d’un projet impressionnant comme
Excel Online, version quasi conforme d’Excel, mais sur le web.
L’ambition de ce livre n’est pas de faire de vous un ninja du JS mais
de vous donner une vision complète du langage pour vous amener à
l’employer du mieux possible, au service de vos utilisateurs. Le but
est également d’élargir votre regard sur d’autres aspects moins liés
à la stricte programmation, comme l’optimisation des performances,
de l’ergonomie, du référencement ou de la rentabilité.
Soyons pourtant humbles et réalistes. Si le JavaScript est au cœur
de l’ergonomie d’un projet web, il n’en demeure pas moins une toute
petite partie d’un ensemble plus vaste regroupant de nombreuses
compétences indispensables à son succès. Des métiers très divers
interviennent au quotidien : graphiste, architecte réseau, rédacteur,
modérateur, community manager, référenceur, commercial,
logisticien, etc. Le développeur web, rarement perçu à la juste
mesure de son importance, n’est toutefois qu’un des rouages de
l’équipe.
2

Hello(s) World(s)

Objectif
Dans ce chapitre, nous allons immédiatement démarrer la
programmation JavaScript via quelques exemples simples et
progressifs. Nous mettrons en place en parallèle
l’environnement de développement complet avec tous les
outils nécessaires pour bien coder et prendre de bonnes
habitudes.

_ 2.1 LES EXEMPLES DE HELLO WORLD


2.1.1 Hello World numéro 1

Rien ne vaut l’incontournable exemple Hello World, qu’on pourrait


traduire par « Salut tout le monde », pour comprendre un langage et
démarrer son utilisation, et le doux frisson de satisfaction quand le
fameux message apparaît pour la première fois à l’écran.
Pour programmer du code JavaScript et l’exécuter, il n’y a rien à
installer. Tout est déjà présent sur votre poste de travail. Pour ce
premier exemple, vous avez simplement besoin d’un éditeur de
texte, comme l’application Bloc-notes de Windows ou l’éditeur de
texte sous macOS, et bien sûr d’un navigateur. Nous verrons plus
loin dans ce chapitre comment optimiser votre environnement de
travail et utiliser tous les meilleurs outils disponibles.
Dans l’explorateur de fichiers, choisissez un répertoire de travail,
appelé par exemple Documents/JavaScript. Créez dans ce dossier
un nouveau fichier au format texte et renommez-le en hello1.html.
Avec un clic droit sur ce fichier, lancez l’éditeur de texte.
Nous avons un fichier vide prêt à être rempli de notre premier
exemple minimaliste :
<html>
<head>
<title>Hello World 1</title>
</head>
<body>
<h1>
<script type="text/javascript">
window.document.write("Hello World");
</script>
</h1>
</body>
</html>

Lancez l’exécution de hello1.html en double-cliquant sur le fichier


dans l’explorateur de fichiers. Le navigateur par défaut s’ouvre et
interprète le code HTML, exécute la ligne de JavaScript et affiche le
rendu.

Le résultat équivalent en HTML est exactement ce qu’on attend :


<html>
<head>
<title>Hello World 1</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>

Ce premier exemple très simple est finalement riche


d’enseignements. Il nous apprend que :
Le code JavaScript est intégré au code HTML classique d’une
page web.
Le JavaScript est placé dans le bloc marqué par les balises
<script type="text/javascript"></script>.
Le JavaScript utilise la notation pointée
window.document.write() pour appeler la fonction write() de
l’objet document, lui-même rattaché à l’objet racine window.
Le JavaScript est interprété et exécuté de manière synchrone,
c’est-à-dire séquentiellement et ligne à ligne par le navigateur.
Une instruction JavaScript se termine par un point-virgule.

2.1.2 Hello World numéro 2

L’exemple hello1.html affichait du texte dans le corps du HTML,


alors que la page n’était pas encore finalisée. L’exemple hello2.html
fonctionne différemment, car le script repère la balise H1 avec son
identifiant et lui affecte un nouveau contenu :
<html>
<head>
<title>Hello World 1</title>
</head>
<body>
<h1 id="monH1"></h1>
</body>
<script type="text/javascript">
document.getElementById("monH1").innerHTML="Hello World";
</script>
</html>
Le résultat est exactement équivalent. À l’issue de l’exécution, la
balise h1 contient bien Hello World. La syntaxe est un peu plus
complexe, tout en restant d’une compacité appréciable. Ici, la
fonction getElementById() recherche et retourne l’élément HTML
ayant l’identifiant monH1 dans le document. La propriété innerHTML
de l’élément trouvé est forcée avec notre message habituel. Nous
verrons en détail dans le chapitre 12 toutes les manipulations des
éléments du document.
Nous aurions dû écrire window.document.getElementById() mais,
pour des raisons de lisibilité et de compacité du code, l’objet racine
window peut être ignoré. Comme dans hello1.html,
document.write() serait suffisant.

2.1.3 Hello World numéro 3

Voyons encore une autre façon d’afficher notre message de


bienvenue dans hello3.html :
<html>
<head>
<title>Hello World 3</title>
</head>
<body id="monBody"></body>
<script type="text/javascript">
var monH1=document.createElement("h1");
monH1.innerHTML="Hello World";
document.getElementById("monBody").appendChild(monH1);
</script>
</html>

Ici, le code HTML du fichier initial ne contient aucune balise h1. Il n’y
a que la balise body avec un identifiant qui nous permettra de la
trouver facilement. Le script crée une variable monH1 avec
l’instruction var et lui affecte un nouvel élément HTML de type H1
grâce à la fonction createElement(). La propriété innerHTML est
ensuite renseignée. Puis la fonction appendChild() sur la balise body
est appelée pour ajouter dans le document l’objet HTML monH1.
Finalement, le document HTML contient grâce à nos trois petites
lignes de JS un nouvel élément h1.
Rassurez-vous, nous verrons toutes les manipulations des éléments
HTML dans le chapitre 12.

2.1.4 Hello World numéro 4

Les possibilités d’afficher notre message de bienvenue par


programmation JavaScript ne sont pas encore épuisées. Voici un
nouvel exemple d’affichage de message par boîte de dialogue :
<html>
<head>
<title>Hello World 4</title>
</head>
<body></body>
<script type="text/javascript">
alert("Hello World");
</script>
</html>

Le rendu est différent car le message ne s’affiche pas dans la page


mais dans une boîte de dialogue gérée par le navigateur :

Une boîte de dialogue est dite modale, c’est-à-dire bloquante, car


elle impose à l’utilisateur une action pour qu’il puisse continuer sa
navigation. Ici, il doit forcément valider le message en cliquant sur le
bouton OK. Nous verrons dans les paragraphes suivants que les
boîtes de dialogues ne sont pas fiables et sont à éviter pour les sites
en ligne.
La fonction alert() est liée à l’objet racine window : on peut donc
écrire window.alert() ou alert() seulement.

2.1.5 Hello World numéro 5

Voyons maintenant un dernier exemple d’affichage de notre


message de bienvenue dans le fichier hello5.html :
<html>
<head>
<title>Hello World 5</title>
</head>
<body></body>
<script type="text/javascript">
var message="Hello World";
console.log(message);
</script>
</html>

À l’exécution, la page reste totalement blanche. Pourtant la fonction


log() de l’objet console a bien affiché le contenu de la variable
message. Voyons maintenant pourquoi et comment utiliser la
console des navigateurs.

_ 2.2 LA CONSOLE DES NAVIGATEURS


Tous les navigateurs modernes proposent aux développeurs web
des outils d’aide à la programmation et au débogage. On parle
souvent de « console JavaScript » mais la console est en fait bien
plus puissante et s’appelle la plupart du temps « Outils de
développement ». Son usage est bien sûr réservé aux phases de
programmation et la console n’est pas affichée pour le visiteur
lambda.
Une combinaison de touches suffit pour faire apparaître la console.
Selon les navigateurs et le système d’exploitation, la combinaison
varie. Voici les cas les plus fréquents :
Pour Chrome sur macOS : ⌘ + ⌥ + J.
Pour Safari sur macOS : ⌘ + ⌥ + C.
Pour Chrome, Firefox et Safari sur Windows : CTRL + MAJ + I.
Pour Internet Explorer et Edge : F12.
Reprenons notre fichier hello5.html, exécuté ici dans Chrome sous
Mac et observons ce que la console affiche :

Sous l’écran de rendu de la page HTML, la console s’ouvre et on


voit apparaître le message de bienvenue affiché avec console.log().
Log signifie « journal » en anglais. Il s’agit d’un terme très utilisé en
informatique, qui désigne un ensemble de données archivées,
généralement des informations techniques sur l’enchaînement
d’opérations de traitements.
Notez que l’interface de la fenêtre de console est légèrement
différente selon les navigateurs. Mais les principales options sont
toujours présentes.

2.2.1 La console JavaScript

La console JavaScript affiche par défaut l’ensemble des erreurs


graves détectées dans la page, qu’il s’agisse d’une erreur de script,
comme ici une variable non définie, ou d’une erreur d’accès à des
ressources sur Internet, comme une image introuvable :
La console est également le moyen idéal pour interagir avec la page
en cours de consultation et les scripts associés. Tapez une
instruction devant le curseur et la console l’exécutera dans le
contexte de la page actuelle et retournera son résultat. Dans le script
hello5.html, la variable message est renseignée. Vous pouvez aussi
accéder au titre de la page avec la notation document.title :

L’objet console accessible en JavaScript permet d’afficher des


informations en cours d’exécution dans le journal de log, dans un
format lisible et tout en restant parfaitement invisible aux visiteurs.
C’est très utile pour contrôler la valeur de données en cours
d’utilisation et pour vérifier que le comportement réel est bien celui
prévu.
Avant la généralisation de la console dans les navigateurs, les
développeurs JavaScript avaient pris la mauvaise habitude d’utiliser
la fonction alert() pour afficher le contenu d’une variable. Il faut
bannir cet usage : il bloque la navigation, affiche le message à tous
les visiteurs et ne peut contenir que du texte brut. De plus, le
message est parfois bloqué par les navigateurs.
Explorons plus en détail les possibilités de la console JavaScript et
ses options.
En cliquant sur le bouton représentant un cercle barré ou parfois une
poubelle, vous videz la zone d’affichage.
En cliquant sur le bouton représentant une roue dentée
d’engrenage, vous ouvrez la fenêtre de paramétrages de la console.
Vous pouvez par exemple ajouter un horodatage, timestamp en
anglais, devant les lignes affichées ou supprimer l’affichage des
erreurs réseaux comme les images introuvables pour vous
concentrer sur la partie script.

Attention, sur certains navigateurs très anciens, l’objet console


n’est pas connu. Son usage déclenche alors une erreur
JavaScript et peut parfois interrompre le reste de vos scripts.

Nous utiliserons dans ce livre plusieurs fonctionnalités de l’objet


console, en plus du très classique affichage par log(). Reportez-vous
à l’index en fin de livre pour les autres cas d’usage.

2.2.2 Les autres outils de la console

L’inspecteur d’éléments se lance avec le bouton représentant une


flèche sur un carré. Il permet de retrouver, dans l’arborescence des
éléments du document, le code HTML correspondant. Par exemple,
sur la balise h1 d’un script initial :
2.2.3 Les performances réseaux

L’onglet Network affiche l’enchaînement des appels réseaux des


différentes ressources que la page charge en cours de construction.
Une représentation graphique complète montre les éventuels points
qui ralentissent le rendu de la page par des temps d’accès
anormalement longs ou des poids trop importants. Cet onglet permet
aussi de limiter artificiellement les débits réseaux en simulant des
accès lents en 2G ou 3G.
Le mode responsive mobile est d’une redoutable simplicité pour
simuler des affichages dans les principales résolutions d’écran
d’iPhones et des divers smartphones et tablettes du marché.
Cliquez sur le bouton représentant un smartphone pour entrer dans
ce mode et choisissez les devices disponibles pour contrôler
l’affichage de votre site.
N’hésitez pas à explorer les options, les filtres et les différents
affichages de la console : elle regorge d’astuces pour gagner du
temps et trouver rapidement une solution à une erreur.

_ 2. 3 L’EXTENSION WEB DEVELOPER


L’extension Web Developer est gratuite et existe depuis le milieu des
années 2000. Elle est utilisée par des millions de développeurs web
dans le monde entier. Il s’agit d’un plugin installable sur les
navigateurs comme Chrome, Firefox et Opera et qui se place sous la
forme d’une icône d’engrenage à côté de la barre de navigation.
En cliquant sur l’icône, vous ouvrez une fenêtre qui comprend de
nombreux onglets correspondant à différents groupes de
fonctionnalités. Impossible de les lister toutes. Notez quelques
fonctionnalités particulièrement utiles pour surligner les éléments
dans la page selon leur type, visualiser l’ensemble des éléments
d’un formulaire, des cookies actifs ou pour supprimer le cache du
navigateur :
Pour l’installer, rendez-vous sur le site de son auteur :
https://chrispederick.com/work/web-developer/.

_ 2.4 L’OUTIL GOOGLE PAGESPEED


Abordons dès maintenant l’outil PageSpeed Insights proposé par
Google. Il mesure les performances de rapidité de chargement d’une
page web, lui attribue un score sur 100 et propose des
recommandations pour améliorer le score, à la fois sur mobile et sur
ordinateur.

Notons immédiatement que le temps où les robots et moteurs de


recherche étaient incapables de comprendre le JavaScript est
largement révolu. Tous les robots sont désormais capables de
voir une page telle qu’un navigateur la présente à un véritable
visiteur.

Nous aborderons plus en détail quelques recommandations issues


de cet outil au prochain chapitre dans le paragraphe qui traite de
l’appel de fichiers JS externes.

_ 2.5 JAVASCRIPT NON ACTIVÉ


L’extension Web Developer permet, parmi de multiples
fonctionnalités, de désactiver simplement l’exécution du JavaScript.
Cliquez sur Disable JavaScript dans le premier onglet pour
désactiver toute interprétation de code JavaScript dans le
navigateur.

Aujourd’hui, il semble inconcevable de naviguer sur le web sans


avoir activé JavaScript. Pour autant, il peut arriver de tomber sur des
visiteurs qui n’acceptent pas les scripts, pour toutes sortes de
raisons (refus d’être suivi, crainte des virus, faible configuration
matérielle). Nous ne pouvons naturellement pas forcer l’activation du
JavaScript, mais nous pouvons les informer de la situation grâce à la
balise noscript :
<html>
<head>
<title>Script et NoScript</title>
</head>
<body>
<script type="text/javascript">
document.write("<h1>Hello World</h1>");
</script>
<noscript>
Votre navigateur n'accepte pas le JavaScript.
<strong>La navigation sur ce site sera impossible !</strong>
</noscript>
</body>
</html>

Si le JavaScript est activé, le bloc HTML entre <noscript> et


</noscript> est simplement ignoré. Si le JavaScript est désactivé, le
bloc HTML est affiché :
La plupart des sites faisant fortement appel au JavaScript utilisent
cette technique pour informer leurs utilisateurs de la nécessité
d’activer les scripts. Facebook affiche un message du même type et
propose en plus une version légère de son interface qui n’utilise pas
le JavaScript :

_ 2. 6 LE BON ÉDITEUR DE TEXTE


Le bloc-notes est un outil vraiment minimaliste pour développer. Il
existe des éditeurs de texte nettement plus adaptés pour travailler
sur des fichiers plus lourds.
La fonctionnalité minimale est la coloration syntaxique. L’éditeur ne
se contente pas d’afficher le texte en noir et blanc, il colorise les
différentes parties selon leur nature et leur usage. Les
commentaires, les chaînes de caractères, les fonctions natives, etc.
sont immédiatement repérés par une mise en forme dédiée.
La complétion automatique est également utile. L’éditeur de texte
anticipe ce que vous tapez et propose en cours de frappe les
possibilités de code. Par exemple si vous commencez à taper
getElement, le logiciel proposera de finir la frappe par toutes les
fonctions possibles comme getElementById,
getElementsByTagName, etc.
De nombreux éditeurs de textes adaptés existent. Citons
NotePad++, Editplus, Sublime Text ou Coda uniquement sous
macOS. La plupart apportent des fonctionnalités de gestion
d’historiques de fichiers et de transfert par FTP des fichiers mis à
jour vers l’hébergeur de votre site. Certains logiciels sont gratuits et
d’autres coûtent généralement quelques dizaines d’euros.
Enfin, il existe des logiciels qui gèrent votre environnement complet
dans des interfaces intégrées, aussi bien sur la partie langage
serveur de type PHP que la partie base de données SQL et bien sûr
la partie HTML, CSS et JS. Ces logiciels sont capables d’interpréter
l’ensemble de votre projet, pendant la phase de codage. La
complétion automatique ne se contente pas des fonctions natives du
langage, mais ajoute les fonctions, les variables et les propriétés de
votre projet. D’autres fonctionnalités d’audit de code, comme la
détection de code mort, c’est-à-dire des portions de code qui ne
peuvent jamais s’exécuter, sont souvent proposées. Ces logiciels
dépassent le plus souvent la centaine d’euros, après une période
d’essai gratuite.

_ 2.7 PROGRAMMER ET PARTAGER


DU JS EN LIGNE
Le partage de code entre développeurs a toujours été contraignant,
en particulier sur les forums. Des outils en ligne sont donc apparus
pour simplifier cette tâche, y compris pour le cas assez complexe de
pages web complètes ayant du code HTML, du code CSS, du code
JavaScript et régulièrement des scripts externes. Ils permettent
d’enregistrer les différentes parties HTML, CSS et JS, de les
sauvegarder dans le cloud, d’avoir le rendu en temps réel et de
partager l’ensemble avec un simple lien, publiable facilement dans
un forum ou dans un mail, sans avoir à ajouter de fichiers joints ou
de copier-coller d’extraits de codes.
Parmi les plus connus, citons https://codepen.io/pen, http://jsbin.com
ou https://jsfiddle.net.

Attention, les scripts publiés sur ces outils sont visibles par tous
les utilisateurs ayant le lien et risquent d’être référencés dans les
moteurs de recherche. Ne publiez pas de code comportant des
données confidentielles comme des clés API, des mots de
passe…

Ces outils sont généralement gratuits pour un usage basique,


suffisant pour un simple partage sur un forum par exemple. Ils
deviennent payants sous forme d’abonnement pour un usage plus
pointu avec sauvegarde de scripts et partages privés.

_ 2. 8 METTRE UN SITE EN LIGNE


Dans la plupart des cas, il n’y a pas de différences de résultats entre
une page HTML exécutée en local, c’est-à-dire depuis le disque dur
du poste de travail, et une page exécutée depuis un site Internet.
Une page HTML exécutée directement depuis le disque dur utilise le
protocole file://, visible dans la barre de navigation au lieu du
protocole classique http(s)://.
Dans certains cas, avec le protocole file://, des systèmes de sécurité
du navigateur limitent les possibilités d’interactions. De plus, en
local, tous les effets liés aux éventuels temps de chargements sont
invisibles.
Il est donc intéressant de tester ses développements dans les
conditions les plus proches de la réalité de l’utilisateur final.
Un développeur travaille généralement sur différents
environnements. L’environnement de test unitaire lui est dédié.
L’environnement de recette, ou de préproduction, est destiné à
partager l’ensemble des développements validés de l’équipe pour
une phase de validation. Une fois validés, les développements
passent sur l’environnement de production, c’est-à-dire le site réel.

Encore une fois, l’informatique utilise des termes concrets pour


parler d’un service virtuel. Le mot production est pourtant
parfaitement adapté. Un site produit par exemple des
commandes, des demandes de devis, des appels téléphoniques,
des passages en magasin, des partages sur les réseaux sociaux
ou des revenus publicitaires.

_ 2.9 LES BONNES PRATIQUES


Avant de progresser dans la maîtrise du JavaScript, terminons ce
chapitre par un rappel des bonnes pratiques à adopter, valables quel
que soit le langage utilisé, pour un développement amateur
personnel ou un projet de plus grande envergure.

2.9.1 Indentation

L’indentation est la mise en forme qui utilise des espacements ou


des tabulations en début de ligne afin de regrouper des éléments de
même niveau de profondeur dans un code. L’indentation permet d’un
seul coup d’œil d’identifier la profondeur d’un groupe d’instructions.
Elle s’applique dans tous les langages. Pour le développeur web,
elle s’applique donc aux codes HTML, CSS et JS.
Prenons l’exemple de deux codes identiques qui met en évidence
l’intérêt de l’indentation. La version sans indentation aligne toutes les
instructions sur le côté gauche :
if (heure<18) {
msg="Bonjour";
style="jour";
} else {
msg="Bonsoir";
style="nuit";
}

La version correctement indentée identifie clairement les blocs


d’instructions selon les résultats du test if :
if (heure<18) {
msg="Bonjour";
style="jour";
} else {
msg="Bonsoir";
style="nuit";
}

Notez qu’une indentation mal réalisée rendra le code


incompréhensible, voire incomplet. En reprenant notre exemple et
en ajoutant la tabulation devant la ligne else, la condition semble
perdre la partie alternative du test :
if (heure<18) {
msg="Bonjour";
style="jour";
} else {
msg="Bonsoir";
style="nuit";
}

L’indentation ajoute nécessairement quelques octets à la taille du


code généré. L’augmentation du poids des pages est infime par
rapport au gain de lisibilité apporté. On peut même aller jusqu’à dire
qu’un code non indenté est parfaitement illisible.
Généralement, le choix du caractère d’indentation est la tabulation,
sur l’équivalent de 2 ou 4 caractères d’espacement. L’avantage de la
tabulation est qu’elle n’occupe qu’un seul octet au lieu de 2 ou 4
pour les espacements.

2.9.2 Programmation
Il existe de nombreuses manières de présenter un bloc
d’instructions, même basique. Imaginons par exemple le test de
condition déterminant s’il faut annoncer « Bonjour » ou « Bonsoir »
au visiteur.
Les trois écritures suivantes sont toutes opérationnelles et
produisent strictement le même résultat.
L’écriture totalement compacte sur une seule ligne est vraiment peu
lisible :
if (heure<18){msg="Bonjour"}else{msg="Bonsoir"}

À son opposé, l’écriture très aérée l’est sans doute trop et occupe
beaucoup de place à l’écran pour un traitement très simple :
if (heure < 18)
{
msg = "Bonjour";
}
else
{
msg = "Bonsoir";
}

L’écriture intermédiaire est sans doute le bon compromis entre


lisibilité et occupation de l’espace à l’écran :
if (heure<18) {
msg="Bonjour";
} else {
msg="Bonsoir";
}

Le principe dans ce style de programmation est que chaque ligne


contient une et une seule instruction. Le lecteur sait ainsi à quoi
s’attendre. Les lignes ne sont pas trop longues et la longueur du
script est restreinte.
Le cas du point-virgule en fin d’instruction est également à trancher.
Il n’est pas nécessaire s’il est suivi d’un retour à la ligne ou d’une
accolade indiquant la fin d’une instruction. On peut donc écrire
indifféremment :
var jour="Bonjour";
var soir="Bonsoir";

ou
var jour="Bonjour"
var soir="Bonsoir"

Mais il faut absolument les points-virgules si l’écriture se fait sur une


seule ligne :
var jour="Bonjour"; var soir="Bonsoir";

La plupart des développeurs ajoutent systématiquement le point-


virgule, probablement à cause du langage PHP qui l’impose sous
peine d’erreur bloquante. Le point-virgule facilite la lecture en
signalant clairement la fin d’une instruction. Il facilite également la
phase de minification du code.
L’ajout du caractère espace est souvent facultatif. Les espaces sont
surtout destinés à faciliter la lecture. Reste à choisir où les placer. Le
bon compromis est probablement un intermédiaire entre cette forme
trop compacte :
if(heure<18){

et cette forme très élastique :


if ( heure < 18 ) {
Minification
La minification est le processus, souvent automatisé par un
outil annexe, qui réduit au maximum le poids d’un script, en
supprimant tous les éléments qui ne sont pas utiles à son
fonctionnement. Les espaces, les tabulations, les retours à la
ligne et bien sûr les commentaires sont supprimés. Le but est
d’accélérer le chargement sur le réseau pour accélérer le
rendu des pages. Tous les développeurs qui mettent à
disposition leurs scripts proposent les deux versions : la
version de travail complète et la version minifiée, souvent
nommée script.min.js.

2.9.3 Règles de nommage

La lisibilité du code est encore améliorée par l’utilisation de noms de


variables et de fonctions qui ajoutent du sens et de la
compréhension. Les développeurs utilisent souvent par habitude des
variables génériques comme i, j et k pour les indices, t pour un
tableau ou n pour un nombre.
Puisque les variables et les fonctions doivent être nommées, mieux
vaut leur donner un nom utile et remplacer t par clients, au pluriel
pour indiquer qu’il s’agit d’un tableau de plusieurs clients, ou n par
nbClient pour indiquer que la variable contient le nombre total de
clients. De la même manière, le développeur pourra prendre
l’habitude de remplacer les indices i, j et k par un nom plus parlant
comme noClient et noCommande, en particulier dans les boucles
imbriquées.

Les accents sont autorisés dans le nom des variables et des


fonctions JavaScript. Les développeurs évitent habituellement de
les employer pour éviter les erreurs d’encodage. Vous trouverez à
l’adresse http://tjs.ovh/var la liste des caractères autorisés pour le
nommage des objets JS.
Nommer judicieusement les éléments rend la lecture plus intuitive et
limite la nécessité d’ajouter des commentaires. Mais des règles de
nommage logiques facilitent aussi la programmation proprement
dite. Si vous savez que vos tableaux sont toujours nommés au
pluriel, vous gagnez du temps en codant. D’autres règles sont aussi
à définir. Décidez si vous utilisez les majuscules ou le caractère
underscore _ pour séparer les mots composant un nom et écrivez
nbClient ou nb_client. La logique est de suivre les règles de
nommage des fonctions natives du JavaScript, avec la première
lettre du nom en minuscule et une majuscule pour identifier les
séparateurs de mots.
Utilisez toujours les mêmes préfixes parmi une liste du type :
nb, is, get, calc, send, save, format…
Nommer en français ou en anglais est un choix personnel qui
dépend de l’ampleur du projet. Un site qui a des ambitions
internationales mérite sans aucun doute un code et des
commentaires entièrement en anglais.

Les règles de nommage s’appliquent à l’ensemble d’un projet


web, depuis le nom des classes CSS et des éléments HTML
jusqu’au langage serveur et aux tables des bases de données.

2.9.4 Commentaires

Les commentaires sont aussi indispensables que l’indentation pour


assurer une lisibilité et une durabilité maximales du code. N’oubliez
pas que vous devrez probablement revoir vos scripts quelques mois
ou quelques années plus tard, après avoir codé des milliers d’autres
lignes. Et que, même si vous n’êtes plus là, un collègue devra se
charger de revenir sur vos travaux, pour des maintenances
correctives (correction de bug) ou évolutives (ajout ou évolution des
fonctionnalités).
En JavaScript, les commentaires sont matérialisés par // pour un
commentaire allant jusqu’à la fin de la ligne en cours et par /* et */
pour englober un morceau de commentaire pouvant s’écrire sur
plusieurs lignes. Voici quelques exemples de commentaires dans
un script :
<script type="text/javascript">
var msg="Hello World"; // Commentaire après une instruction
// Commentaire sur toute une ligne
/*
Ligne 1 de commentaire
Ligne 2
Ligne 3
*/
</script>

Un commentaire pertinent ne doit pas répéter ce que le code dit déjà


de manière évidente. Le commentaire /* Boucle sur le tableau */ est
parfaitement inutile. En revanche, rappeler en quelques mots ce que
fait la boucle et son but doit permettre de comprendre l’esprit du
code plus rapidement que par l’analyse de l’ensemble des lignes
concernées.
Les commentaires sont utilisés également pour signer son code pour
toute sa durée de vie. Cette signature est engageante sur la qualité
et la lisibilité de ce que le développeur produit et met à disposition.

La mise en ligne d’un script est appelée livraison. Comme pour


l’analogie client/serveur, l’informatique utilise souvent des mots du
quotidien concret.

L’habitude veut que chaque fichier d’un projet débute par une courte
introduction sous forme de commentaires indiquant le but du fichier,
sa version, l’auteur, les travaux en cours…
Il peut aussi être judicieux d’utiliser les commentaires pour signaler
les grandes parties d’un traitement ou d’un fichier.
Pour rappel, les commentaires sont aussi pertinents en dehors du
JavaScript. En CSS, ils sont encadrés par /* et */, obligatoires
même sur une seule ligne. En HTML, ils sont à placer entre <!-- et
-->.

2.9.5 Réutilisabilité

La magie de l’informatique est qu’elle permet de réutiliser très


facilement une fonctionnalité déjà écrite une fois par vous-même ou
par un développeur qui partage ses créations sur le net. Dans le
chapitre suivant, nous verrons comment, avec la création de
fonctions et de fichiers du type .js. Cela implique un peu de travail et
d’organisation et trop souvent la solution de facilité est de
simplement copier-coller le bout de code à chaque fois qu’il faut le
réutiliser. Rapidement, des doublons apparaissent, alourdissant le
poids total du code et rendant fastidieuse la maintenance : au lieu de
corriger la fonctionnalité une fois pour toutes, il faut identifier les
différents endroits où elle est utilisée et appliquer autant de
correctifs.

2.9.6 Cohérence

La lisibilité est également renforcée par une cohérence dans la façon


de programmer et de nommer ses fichiers, ses fonctions et ses
variables. Il n’y a pas vraiment de bonnes ou de mauvaises règles.
Le plus important est de garder une cohérence tout au long d’un
projet.
Le plus difficile est sans aucun doute d’assurer la cohérence dans le
cas d’un travail à plusieurs personnes ayant des parcours divers. Le
choix des règles est souvent imposé par un historique ou par un
pouvoir hiérarchique, même si ces règles ne plaisent pas à tous. Il
faut malgré tout s’y plier à chaque ligne de code et tenir dans la
durée.
Dans ce livre, nous utiliserons les règles suivantes :
Indentation sur deux caractères.
Nommage en français du type getNbClient() pour retourner
nbClient du tableau clients.
Une instruction par ligne.
Un point-virgule systématique en fin d’instruction.
Un usage des espaces pour séparer les mots clés comme if et
devant les {.

2.9.7 Travail en équipe

Aujourd’hui, tous les projets utilisent un logiciel de gestion de


versions. Après quelques années, Git, développé par le fameux
Linus Torvalds, a supplanté les autres, comme SVN. Voici quelques
fonctionnalités de Git :
Il gère l’historique de toutes les modifications d’un projet.
Il facilite les retours en arrière.
Il assure la détection et la correction assistée de modifications
concurrentes, c’est-à-dire de modifications effectuées par
plusieurs personnes sur la même portion de code.
Il est décentralisé pour être hébergé dans un lieu sécurisé et
assurer la sauvegarde ou la redondance des données.
Il peut être synchronisé avec d’autres logiciels pour effectuer
des traitements supplémentaires (FTP, mail, minification…).
Naturellement, l’usage de Git est tout à fait recommandé et apporte
toutes les fonctionnalités pour un développeur seul sur son projet.
Le serveur Git s’installe sur un serveur distant, sur un NAS ou sur un
ordinateur personnel. Chaque utilisateur doit installer un client Git
sur son poste de travail.
L’apprentissage de l’administration serveur de Git nécessite du
temps et une bonne maîtrise du l’utilisation des lignes de
commandes. C’est pourquoi sont apparus des services web dans le
cloud simplifiant la configuration. Le plus largement utilisé est
GitHub.
Le modèle économique de GitHub est particulièrement malin car il
sert un double objectif. GitHub est gratuit pour les projets dont le
code est ouvert, c’est-à-dire public. Grâce à cette ouverture,
l’audience du service est considérable avec plus de 20 millions
d’inscrits. Parmi cette audience habituée à utiliser l’outil, une
partie achète la version payante, qui permet de créer des projets
privés accessibles uniquement aux membres de l’équipe du projet
et qui doivent aussi être abonnés payants.
3

Structure d’un script

Objectif
Dans ce chapitre, nous allons apprendre à intégrer du
JavaScript dans une page web, en tenant compte de son
impact sur le référencement dans les moteurs de recherche.
Nous allons aussi découvrir l’ensemble des structures de
script pour réaliser des traitements conditionnés ou répétitifs.
Les variables et les fonctions n’auront plus de secrets pour
vous. Notez que ces grandes notions de base du JavaScript
sont tout à fait réutilisables dans les autres langages de
programmation.

_ 3.1 INTÉGRER DU JAVASCRIPT


3.1.1 Dans une page HTML

Nous avons vu au chapitre précédent les cinq exemples Hello World


qui montrent que le code JavaScript s’intègre tout simplement au
cœur du HTML avec les balises <script> et </script>.
La balise <script> attend l’attribut type, qui indique au navigateur le
langage utilisé. L’attribut type="text/javascript" est explicite. La balise
contient du texte à interpréter en JavaScript.
La norme HTML veut que les tags de balises et les noms des
attributs soient écrits en minuscules.

Le code HTML placé entre les balises <noscript> et </noscript> n’est


affiché que pour un navigateur ne supportant pas le JavaScript. La
partie <noscript> est facultative. Elle permet simplement d’informer
le visiteur que son navigateur est mal configuré. Voir le paragraphe
JavaScript non activé ici pour un exemple complet.

Version du JavaScript sur le navigateur


Il n’existe pas de moyen en JavaScript pour obtenir la version
du JavaScript disponible sur le navigateur de l’utilisateur. Il
est recommandé de tester l’existence d’une fonctionnalité
plutôt que de tenter d’obtenir un numéro de version.

3.1.2 Dans un tag HTML

Le JavaScript s’intègre également directement dans les balises


HTML, le plus souvent dans les attributs dédiés à la gestion des
événements. Par exemple, sur cet élément HTML, le clic est détecté
par l’attribut onclick, qui déclenche l’incrément d’une variable de
compteur et son affichage dans la console.
<div onclick="compteur=compteur+1; console.log(compteur);">
Cliquez-moi
</div>

Notez que nous avons utilisé le point-virgule pour séparer les


instructions et ainsi déclencher deux actions avec un seul clic.

3.1.3 Grâce à un fichier externe

L’intégration de JavaScript via un appel à un fichier externe est la


méthode la plus utilisée et celle qui offre le plus de possibilités, tout
en ayant des impacts potentiellement sensibles sur la rapidité de
l’affichage des pages et sur le référencement.

◆ La création du fichier .js

Un fichier externe de code JavaScript n’est qu’un fichier texte


contenant l’intégralité du code placé entre les balises <script> et
</script>, mais pas les balises <script>.

L’utilisation de l’extension .js n’est qu’une convention, non obligatoire. Elle a


néanmoins le double avantage d’être intuitive à la lecture du code et force votre
éditeur de texte à lui appliquer la coloration syntaxique dédiée au JavaScript.

Commentaires
Pensez à commencer votre fichier .js par quelques lignes de
commentaires rappelant les grandes fonctionnalités du fichier
et dans quels cas il est utilisé.

Reprenons un exemple Hello World avec la boîte de dialogue


alert() :
<html>
<body>
<script type="text/javascript">
alert("Hello World");
</script>
</body>
</html>

Le fichier hello.js correspondant contient simplement cette ligne :


alert("Hello World via un fichier JS");

◆ L’appel au fichier.js
Le code de notre page Hello World qui fait appel au fichier hello.js
devient :
<html>
<body>
<script type="text/javascript" src="hello.js"></script>
</body>
</html>

La balise <script> est toujours utilisée pour désigner un appel à du


code JavaScript. Cette fois-ci, l’utilisation de l’attribut src, abréviation
de source, indique au navigateur que le code n’est pas entre les
balises mais dans le fichier référencé à l’adresse source.
Le fichier .js est stocké sur le serveur du site. Ici, il est enregistré
dans le même répertoire que la page HTML. L’habitude veut que les
fichiers .js soient stockés au même endroit pour faciliter leur appel,
par exemple le répertoire javascript du répertoire ressources,
souvent assets en anglais, à la racine du site. L’appel au fichier
externe s’écrirait alors avec le caractère / en début d’adresse pour
indiquer au navigateur de remonter à la racine du site :
<script type="text/javascript" src="/assets/javascript/hello.js"></script>

Naturellement, cet exemple est trivial, mais il montre l’intérêt


d’utiliser un fichier externe contenant du code JavaScript. Il suffit de
l’écrire une seule fois et de l’appeler avec une simple ligne à chaque
page où il est nécessaire. Généralement, un site emploie un fichier
externe .js sur l’ensemble de ses pages pour la gestion de la
navigation, des menus et des différents effets de style.
Il est possible d’appeler un script hébergé sur un autre domaine en
indiquant dans l’attribut src le chemin d’accès complet :
src="https://cdnjs.cloudflare.com/libs/jquery.min.js"

Il existe une autre écriture possible, sans la partie http(s):, qui


indique au navigateur de choisir le protocole HTTP ou HTTPS utilisé
par la page appelante :
src="//cdnjs.cloudflare.com/libs/jquery.min.js"
CDN, ou Content Delivery Network
Un CDN est un réseau de diffusion de contenus spécialisé
dans la diffusion de ressources statiques telles que les
fichiers .js, des images, des logos… Ils sont destinés à
améliorer la vitesse de transfert sur le réseau, grâce à des
implantations dans toutes les zones géographiques, au plus
près des utilisateurs.

◆ L’appel en mode synchrone

L’utilisation d’un fichier externe oblige le navigateur à faire un nouvel


appel HTTP pour récupérer le contenu du fichier. Vous pouvez vous
reporter au schéma de communication client/serveur, qui explique ce
mécanisme.
Cet appel génère un délai pour envoyer la requête et récupérer le
résultat. Pendant ce temps, le navigateur ne fait rien d’autre et
attend le retour. Cet appel en mode synchrone garantit que le fichier
est bien chargé avant de poursuivre le rendu de la page. Cette
garantie se fait au prix d’une latence sur la vitesse du rendu. La
latence est plus ou moins longue selon la taille du fichier .js à
récupérer et surtout selon la vitesse de connexion de l’utilisateur.

◆ L’appel asynchrone

Par défaut, et sans indication contraire, un appel à un fichier .js


externe se fait en mode synchrone : le navigateur attend la fin du
chargement pour poursuivre le rendu HTML. Il est possible de forcer
les appels à des fichiers JavaScript en mode asynchrone. Dans ce
cas, l’interprétation du code HTML n’est pas bloquée par l’appel et le
navigateur poursuit le rendu de la page, tout en chargeant en
parallèle le ou les fichiers .js externes. Une fois les fichiers chargés,
ils sont interprétés et prêts à être utilisés.
Le mode asynchrone accélère grandement le rendu de la page. Mais
il est impossible de prévoir à quel moment le fichier sera
effectivement chargé et quand les fonctionnalités JavaScript
présentes seront disponibles.
Pour forcer l’appel en mode asynchrone, il suffit d’ajouter l’attribut
async dans la balise <script> :
<script async type="text/javascript" src="fichier.js"></script>

Dans ce mode, le navigateur lance le chargement des fichiers et les


interprète dès qu’ils arrivent, sans se préoccuper de l’ordre d’appel
initial.
L’attribut defer, de l’anglais to defer, qui signifie « reporter », entraîne
un fonctionnement un peu différent. Le chargement des scripts est
reporté à la fin de l’interprétation du document. L’ordre d’appel des
scripts est respecté. Cet attribut a peu d’intérêt finalement : il suffit
de placer les appels au fichier .js externe à la toute fin du code
HTML pour obtenir le fonctionnement équivalent.

3.1.4 L’impact du JavaScript sur le référencement

L’utilisation du mode asynchrone oblige à coder différemment et à


s’assurer via la détection d’événements de fin de chargement que
les fonctionnalités JavaScript ne seront pas appelées trop tôt, avant
leur définition, sous peine d’erreur d’exécution.
Malgré tout, l’augmentation des performances sera réellement
sensible, à la fois pour les utilisateurs, qui ressentiront une plus
grande fluidité de navigation, et pour les moteurs de recherche.

◆ Le cache des navigateurs

Les navigateurs utilisent un mécanisme de cache de fichiers pour


stocker en local sur le disque dur les fichiers qui ont déjà été
appelés. Cette technique réduit par un facteur proche de 100 le
temps nécessaire pour charger un fichier par rapport à un appel
réseau. L’ensemble des types de fichiers est concerné par le cache,
comme les fichiers .js, .css et les images. Le cache rend donc la
navigation bien plus rapide pour un visiteur récurrent. Le cache n’a
aucun effet accélérateur pour une première visite.
Le développeur web peut configurer son serveur web pour définir le
temps de conservation dans le cache, évitant ainsi de subir les
latences réseaux. Plus le temps fixé est long, moins il y aura
d’appels réseaux au cours des futures visites. En revanche, le
navigateur ne détectera pas une nouvelle version de fichier avant un
délai important.
Google PageSpeed recommande d’affecter au moins sept jours de
durée de vie aux fichiers externes. En dessous de cette durée, des
pénalités sont appliquées au score de performance de la page.

Notez que Google ne s’impose pas lui-même les


recommandations sur la durée de vie des fichiers dans le cache.
Les fichiers .js de mesure d’audience de Google Analytics par
exemple n’ont qu’une durée de vie de quelques heures. Cette très
faible valeur permet à Google de diffuser très rapidement des
mises à jour de code sur tous les sites utilisant ses services.

◆ Les fichiers CSS et le référencement

Profitons de ce paragraphe pour faire un parallèle avec les fichiers


CSS. Il n’existe pas de système de chargement asynchrone des
fichiers de propriétés CSS.
Google PageSpeed recommande de placer les styles absolument
nécessaires pour un rendu minimaliste rapide dans le corps de la
page, et le reste dans un fichier CSS, chargé en fin de code HTML.
Cette recommandation est facile à mettre en place pour les pages
très simples de résultats de recherche que Google propose. Cela
devient réellement problématique pour un site éditorial proposant du
contenu riche avec des mises en forme variées.
Attention à ne pas charger un script externe dont vous ne
connaissez pas la nature. Le script aura potentiellement accès
aux données de vos utilisateurs comme les cookies ou les mots
de passe de votre site. Un script malveillant posé sur un site est
considéré comme une faille de sécurité. Les moteurs de
recherche les repèrent et signalent les sites dangereux dans les
résultats de recherche, avec des effets immédiats et dramatiques
sur l’audience.

_ 3.2 LES VARIABLES


La programmation, quel que soit le langage, consiste toujours à
manipuler des données et des informations afin de produire un
résultat en sortie de traitement :
Un site web utilise une base de données et les clics de
l’utilisateur pour afficher les informations demandées.
Un jeu vidéo utilise les commandes de la manette pour afficher
des images à l’écran.
Le logiciel de guidage de fusées utilise les données des
capteurs de vol pour envoyer des consignes de poussée aux
réacteurs.
Pour stocker ces données, le développeur crée et utilise des
variables. Une variable est une zone de mémoire destinée à stocker
une information. Le terme variable a été choisi pour rappeler que la
donnée varie au cours de l’exécution du script.
Une variable est définie par un nom choisi par le développeur. Un
nom de variable doit commencer par une lettre et peut contenir
ensuite des lettres, en majuscules ou en minuscules, des chiffres et
quelques caractères spéciaux comme $ ou _. Le caractère tiret n’est
pas autorisé car il correspond à l’opérateur de soustraction.
N’oubliez pas de respecter les règles de nommage vues dans la
partie des bonnes pratiques.

Le nom donné à une variable ne peut être un mot réservé par le


langage, comme var, if ou window.
Les noms de variables infos, chienAge, chien2_age sont valides.
En revanche else, 2B3 ou a*b ne sont pas autorisés.

3.2.1 Déclaration de variables

La phase de création d’une variable est appelée déclaration. Elle se


fait avec l’opérateur bien nommé var.

Sensibilité à la casse
La casse est la distinction entre majuscule et minuscule. Le
JavaScript est sensible à la casse, c’est-à-dire que les
variables nom, Nom et NOM sont trois variables différentes,
contenant des données différentes. Cette sensibilité est
source d’erreur à l’exécution à partir d’une simple erreur de
frappe. De bonnes règles de nommage sont un bon moyen
de les limiter.

La phase de déclaration est souvent mise à profit pour initialiser la


variable, c’est-à-dire lui donner une valeur initiale.
Par exemple, cette instruction déclare une variable compteur et
l’initialise à 0 :
var compteur=0;

Il est possible aussi de déclarer plusieurs variables avec un seul


appel à var, en séparant les variables par une virgule :
var compteur=0, texte="Hello", maxi=100;
Comme dans tous les langages de programmation, la déclaration de
variable est indispensable. Utiliser une variable non déclarée
entraîne une erreur à l’exécution du type ReferenceError
variableInconnue is not defined, qui s’affiche dans la console. Le
message d’erreur varie selon le navigateur utilisé mais l’erreur se
produira systématiquement.
Si la déclaration est obligatoire, l’initialisation peut se faire plus tard
dans le déroulement du script. On peut donc utiliser var sans
l’affectation de valeur :
var compteur, texte, maxi;

Dans ce cas, le contenu des variables n’est pas encore connu. Le


navigateur sait simplement qu’une variable a été créée et pourra être
utilisée.

Contenant et contenu
Ne confondez pas le nom de la variable et son contenu.
console.log("compteur") affichera la chaîne de caractères
"compteur". Tandis que console.log(compteur) affichera le
contenu de la variable compteur.
La coloration syntaxique de l’éditeur de texte distingue les
chaînes de caractères des variables.

3.2.2 Les types de variables

Une variable JavaScript peut contenir une grande variété de types


de données, comme des nombres, des chaînes de caractères, des
dates, des tableaux ou un objet complexe. Le langage JavaScript est
« faiblement typé », c’est-à-dire qu’il autorise une variable à contenir
n’importe quel type de données et à en changer en cours
d’exécution. Cette souplesse, qu’on peut même qualifier de laxisme,
est difficile à concevoir pour un développeur habitué aux langages
classiques, nettement plus stricts.
Si vous respectez des règles de nommage, vous savez, en tant que
développeur, ce que peut contenir une variable à partir de son nom.
Cependant aucun mécanisme de contrôle JavaScript ne garantit une
cohérence entre les noms et les contenus.
Le fonctionnement d’une simple opération d’addition peut avoir des
effets étranges si le type de la variable n’est pas cohérent. Explorons
un cas intéressant avec un petit script :
var n1=10, n2=20, n3="5";
console.log(n1+n2);
console.log(n3+n1);

Le développeur s’attend à ce que la console contienne 30 (10+20) et


15 (5+10). Pourtant le second résultat est 510 :

La subtilité vient de la déclaration de la variable n3. À cause de la


notation n3="5", n3 est une chaîne de caractères. L’opérateur +
réalise alors une concaténation, c’est-à-dire qu’il assemble le texte
des deux parties de l’opération et on obtient littéralement les
caractères 5 et 10 à la suite.
Nous verrons en détail dans les prochains chapitres comment
manipuler les nombres, les chaînes de caractères et les autres
objets contenant des informations.

◆ L’opérateur typeof

Le JavaScript propose l’opérateur typeof, qui retourne une chaîne de


caractères avec le type du contenu d’une variable. Ce script définit
des variables et affiche leur type JavaScript :
<html>
<head>
<title>Programmation</title>
</head>
<body>
<h1 id="monH1"></h1>
<script type="text/javascript">
var a;
var n1=10;
var msg="Hello World";
var h1=document.getElementById("monH1");
console.log(typeof a); // retourne undefined
console.log(typeof n1); // retourne number
console.log(typeof msg); // retourne string
console.log(typeof h1); // retourne object
</script>
</body>
</html>

Notez que typevof est un opérateur qui s’applique à l’élément


suivant et pas une fonction. Il s’utilise donc sans les parenthèses
avec la syntaxe typeof variable et non typeof(variable).

◆ Le type undefined

Dans l’exemple, la variable a est déclarée sans être initialisée. Le


type de contenu ne peut pas être défini. L’opérateur typeof retourne
donc undefined.

◆ Le type number

Le JavaScript ne distingue pas les différents types de nombres


comme dans d’autres langages. Les petits entiers, les très grands
entiers et les nombres à virgule sont tous de type number.
Les variables de type number peuvent être manipulées avec les
fonctions mathématiques classiques.
◆ Le type string

Le type string désigne les chaînes de caractères, identifiées par les


séparateurs " qui délimitent le contenu de la chaîne. Comme pour
les nombres, il n’y a pas de notion de taille ou de longueur en
JavaScript. Une chaîne de 1 ou de 1 000 caractères est de type
string.
JavaScript propose de très nombreuses fonctionnalités de
manipulation de chaînes de caractères, que nous verrons dans le
chapitre dédié suivant.

◆ Le type object

Le type object est un type générique pour définir une structure de


données complexe. Un objet est un ensemble organisé de propriétés
et de fonctionnalités.
Comme le JavaScript est un langage objet, ce type est central dans
le langage et nous lui accorderons le chapitre 8.

◆ Le type boolean

Le type boolean, pour « booléen » en français, vient du


mathématicien anglais Boole, inventeur de l’algèbre de Boole dès
1854. L’algèbre de Boole s’applique aux opérations de logique sur
des variables ne pouvant prendre que deux états, vrai (true) ou faux
(false). Inventé plus d’un siècle avant l’informatique, il est encore au
cœur de tous les tests et des embranchements dans tous les
langages.
Nous l’aborderons donc en détail aux paragraphes suivants, qui
concernent les tests conditionnels et les boucles.

3.2.3 Les constantes

Une constante est comparable à une variable qui ne peut pas être
modifiée une fois créée. Une constante est déclarée par const avec
une valeur d’initialisation obligatoire. Si le script tente de changer la
valeur de la constante, une erreur du type TypeError: Assignment to
constant variable est déclenchée.
Une constante dans un script peut être, par exemple, le taux de
conversion entre le franc et l’euro.
Il y a un double intérêt à utiliser une constante à la place d’une
variable qui ne sera jamais modifiée à l’exécution :
Le développeur indique directement dans le code que la
constante est destinée à être fixe.
Le navigateur s’assure à l’exécution qu’une erreur de
programmation ne viendra pas modifier cette valeur.

_ 3.3 LES BLOCS D’INSTRUCTIONS


Nous avons déjà vu qu’une instruction JavaScript se termine par un
point-virgule ou un saut de ligne. En JavaScript, comme dans de
nombreux langages, le regroupement d’instructions se fait à
l’intérieur d’un bloc commençant par une accolade ouverte { et se
terminant par une accolade fermante }. Dans un script, il y a
obligatoirement autant d’accolades ouvrantes que fermantes. Un
nombre différent entraîne nécessairement une erreur.
Puisque le saut de ligne signifie une fin d’instruction, il n’est pas
possible d’écrire une instruction sur plusieurs lignes comme dans
d’autres langages. Une erreur sera déclenchée. Si, pour des raisons
de mise en page, une instruction est écrite sur plusieurs lignes dans
ce livre, il faut considérer qu’elle tient sur une seule ligne dans le
script.
Le regroupement d’instructions est très utile car il permet d’organiser
les traitements composés d’un nombre plus ou moins élevé
d’instructions, selon différents embranchements.
Il existe des variables dont la portée se limite au bloc d’instructions
courant dans lequel elles sont déclarées.
La portée d’une variable est l’ensemble des éléments du code
source où la variable existe et est manipulable.

Quand on déclare une variable avec let au lieu de var, sa portée est
limitée au bloc où la déclaration est faite. Voici quelques exemples
de portées de variables créées avec let :
let a=1, b=1; // Déclaration dans le bloc de plus haut niveau
console.log(a); // affiche 1
if (a==1) {
let b=0; // Déclaration de b dans un bloc inférieur
console.log(a); // affiche 1
console.log(b); // affiche 0
}
console.log(b); // affiche 1

Une variable déclarée avec let est visible dans les blocs de plus bas
niveau. Dans le bloc de condition if, une nouvelle variable b est
créée et initialisée à 0. La variable b du bloc supérieur n’est pas
affectée à la fin du bloc if et vaut toujours 1.

Pour limiter les risques de confusion et d’erreur à l’exécution, il


est recommandé d’éviter de nommer de la même façon des
variables ayant des portées différentes.

Nous verrons en détail dans le paragraphe sur les fonctions les


différents niveaux de portée des variables.

_ 3.4 LES TESTS CONDITIONNELS


Un programme est composé d’une série d’instructions qui
s’enchaînent séquentiellement les unes à la suite des autres. Les
tests conditionnels permettent d’orienter l’exécution vers des
embranchements d’instructions selon le résultat de conditions
définies par le développeur.

3.4.1 Le test simple

Dans le cas d’un test conditionnel simple, le bloc d’instructions n’est


exécuté que si le résultat de la condition est vrai.
Imaginons le calcul de frais de port sur un devis. Si le montant total
est supérieur ou égal à 100 euros, les frais de ports sont offerts :
if (montant>=100) {
var port=0;
}

L’instruction if attend une condition, écrite entre parenthèses. Si la


condition est remplie, c’est-à-dire si le résultat de la condition vaut
true, le bloc d’instructions du if s’exécute. Si la condition vaut false,
le bloc d’instructions est ignoré.
Quand il n’y a qu’une seule instruction dans le bloc à exécuter,
comme c’est le cas dans cet exemple, il est possible de ne pas
utiliser les accolades. Seule l’instruction directement après la
condition est exécutée. Les écritures suivantes auraient le même
résultat :
Soit sur une seule ligne :
if (montant>=100) var port=0;

Soit, plus classiquement, avec une ligne par instruction :


if (montant>=100)
var port=0;

L’inconvénient de cette écriture compacte est que vous ne pouvez


pas ajouter une autre instruction liée à la condition. Seule
l’instruction placée immédiatement après le test est conditionnée.
Les autres sont exécutées systématiquement. Le choix de l’ajout
systématique d’accolades dépend des règles de programmation que
vous avez définies dans le projet.
3.4.2 Le test si - sinon

Le test simple s’accompagne souvent d’un bloc d’instructions


exécutées si la condition initiale n’est pas remplie. Si la condition du
if n’est pas true, les instructions du bloc if sont toujours ignorées et
celles du bloc else sont lancées. Inversement, si la condition du if est
vraie, les instructions else ne sont pas exécutées.
Reprenons notre exemple de calcul de frais de port. Si le montant
est supérieur ou égal à 100 euros, le port est offert, sinon il est d’un
montant forfaitaire de 6,90 euros :
if (montant>=100) {
var port=0;
} else {
var port=6.9;
}

Comme pour le test simple, l’utilisation des accolades n’est pas


obligatoire. Ce test peut aussi s’écrire :
if (montant>=100) var port=0;
else var port=6.9;

Même si cette écriture n’est pas très lisible, elle est régulièrement
utilisée pour sa compacité et l’économie de quelques octets réalisée.
Le JavaScript ne possède pas de condition elseif comme d’autres
langages. Il est cependant possible de faire un enchaînement de
conditions en ajoutant un espace entre else et if pour obtenir else if.
Imaginons que notre calcul de frais de port nécessite finalement
deux paliers. Un premier à 6,90 pour moins de 50 euros d’achat et
un second à 3,90 pour moins de 100 euros et toujours le port offert
au-delà. Le calcul de port pourrait s’écrire :
if (montant>1=00) {
var port=0;
} else if (montant>=50) {
var port=3.9;
} else {
var port=6.9;
}

Les tests conditionnels peuvent vite devenir ardus à écrire et à


comprendre. Le paragraphe sur les conditions présente en détail
tous les opérateurs de conditions et la manière de les rendre les plus
compréhensibles possible.

3.4.3 Les tests multiples

Il existe une autre manière de traiter des tests multiples, cette fois-ci
sur des conditions d’égalité. L’écriture est particulière et présente
cette structure :
switch (expression) {
case valeur1:
blocInsctructions1
break;

case valeurN:
blocInsctructionsN
break;
default:
blocInsctructionsDefault
}

L’instruction switch démarre le bloc conditionnel en testant la valeur


retournée par expression. Une expression est un traitement sur des
données qui retourne un résultat. Cela peut être soit une variable
simple, soit un calcul sur plusieurs variables.
Le résultat de l’expression est comparé aux valeurs de chaque bloc
case. Si la valeur de expression est égale à celle d’un des case, son
bloc d’instructions est exécuté. Notez qu’ici il n’y a pas d’accolades
pour marquer les blocs. La fin des instructions est matérialisée par
l’instruction break. Si le break est omis, l’exécution se poursuit avec
la valeur suivante du prochain case.
Si aucune valeur n’est trouvée, le bloc facultatif default est exécuté.
Voici un exemple d’utilisation de switch et case sur le jour de la
semaine pour afficher un message d’information dans la balise h1 :
<h1 id="monH1"></h1>
<script type="text/javascript">
var dt=new Date();
var jour=dt.getDay();
var msg="";
switch (jour) {
case 1:
msg="Dur le lundi";
break;
case 0:
case 6:
msg="C'est le week-end !";
break;
default:
msg="Pleine semaine : au boulot";
break;
}
document.getElementById("monH1").innerHTML=msg;

La variable dt est initialisée avec un objet de type Date contenant la


date et l’heure du navigateur de l’utilisateur. La variable jour contient
l’indice du jour de semaine de la date dt. Notez qu’en JavaScript,
comme dans les pays anglophones, la semaine commence le
dimanche. La valeur 0 désigne donc dimanche, la valeur 1 le lundi,
jusqu’à 6 pour le samedi. Nous verrons la manipulation des dates en
JavaScript au chapitre 6.
Le bloc de conditions switch commence avec le case 1. Le message
« Dur le lundi » est stocké dans la variable msg. L’instruction break
termine le bloc conditionnel switch.
Le case 0, qui correspond au dimanche, ne contient aucune
instruction de traitement, ni le break attendu, et se poursuit
directement avec le case 6, qui renseigne le message de week-end.
Cette notation est équivalente au test « jour vaut 0 ou 6 ».
Enfin, un bloc default renseigne le message pour les autres cas qui
n’ont pas encore été rencontrés, ici la semaine travaillée du mardi au
vendredi.
Finalement, le contenu de msg est placé dans la balise h1, ayant
l’identifiant monH1, et s’affiche à l’écran.

3.4.4 L’opérateur ternaire

L’opérateur ternaire doit son nom aux trois parties qui composent
son écriture. Il permet de tester une condition et de retourner une
valeur si la condition est remplie et une autre si la condition est
fausse. Sa syntaxe utilise le point d’interrogation ? pour signaler la
fin de la condition et le caractère deux-points : pour séparer les deux
valeurs de retour possibles :
Condition ? valeurSiVrai : valeurSiFaux

Notre exemple de calcul de frais de port est idéal pour utiliser


l’opérateur ternaire :
var port= montant>=100 ? 0 : 6.9;

La compacité est imbattable. Un lecteur qui ne connaît pas cet


opérateur aura du mal à comprendre le fonctionnement. Les
espaces sont ici présents pour faciliter la lecture des trois parties de
l’opérateur. La syntaxe compactée au maximum est valide :
var port=montant>=100?0:6.9;

Le gros avantage de l’opérateur ternaire est qu’il peut être utilisé au


sein d’une autre instruction. Il n’y a pas besoin d’écrire un test
conditionnel plus haut dans le script pour utiliser le résultat. Cette
ligne affiche directement une chaîne de caractères formée de la
concaténation d’éléments avec le pluriel au mot « article » si nb est
supérieur à 1.
console.log("Votre devis comporte " + nb + " article" + (nb>1?"s":"") );

Le pluriel est traité avec cette simple portion nb>1?"s":"". Vous


reconnaissez l’opérateur ternaire qui teste la condition sur la valeur
de nb et retourne s ou une chaîne vide.

_ 3.5 LES CONDITIONS


Nous avons vu comment utiliser les conditions pour orienter le script
vers les blocs de code appropriés. Attardons-nous sur l’écriture des
conditions avec la manipulation des variables de type boolean, partie
capitale des notions que tout développeur doit absolument maîtriser.

3.5.1 Définition d’une condition

Nous avons déjà vu dans le paragraphe des types de variables


qu’une variable booléenne ne pouvait prendre que deux valeurs, true
ou false. Une expression de condition, présente dans les tests
conditionnels, retourne une valeur booléenne.
Une variable peut recevoir le résultat d’une expression de test de
condition :
var isMajeur = age>=18;

Ici la variable isMajeur vaut true si age est supérieur ou égal à 18 et


false dans le cas contraire. Le type de isMajeur, obtenu avec typeof
isMajeur retourne la chaîne de caractères boolean.
La variable isMajeur est utilisable dans un test conditionnel :
if (isMajeur) {
msg="Bienvenue";
} else {
msg="Accès interdit";
}

Le JavaScript étant très souple, une condition est considérée


comme vraie si elle retourne toute autre valeur que 0. La condition
est fausse si elle contient 0 :
if (nbArticle) {
msg="Votre panier contient "+nbArticle+" article(s)";
} else {
msg="Panier vide";
}

La condition se comporte comme si on avait écrit if (nbArticle>0).


Le test sur une variable non déclarée ou non initialisée retournera
false. Le test if est une bonne solution pour contrôler l’existence d’un
élément JavaScript.

◆ Opérateur d’égalité

L’opérateur d’égalité dans une condition est le signe égal doublé ==.
Il est doublé pour matérialiser la comparaison et non l’affectation
d’une valeur à une variable. Voici quelques exemples de
comparaisons d’égalité :
var nb=5;
nb==5; // true
nb==6; // false
nb=="5"; // true
nb==10/2; // true

Une erreur courante, même pour les développeurs aguerris, est de


rater l’écriture de l’égalité et de la remplacer par une affectation.
Cette erreur entraîne un double dysfonctionnement :
Le test est toujours vrai, car une affectation réussie retourne
true.
La variable à tester change de valeur.
À l’issue de ce script, quelle que soit la valeur initiale de la variable
nb, nb vaut 10 et la variable msg vaut « Niveau maximum atteint » :
var nb=5;
if (nb=10) { // Ici une affectation et pas une comparaison
var msg="Niveau maximum atteint";
}
console.log(nb);
console.log(msg);

◆ Opérateur d’égalité de type et de contenu

Il existe un niveau supérieur d’égalité qui consiste à comparer, en


plus des contenus, les types des deux parties du test d’égalité. Vous
avez peut-être noté dans l’exemple plus haut que le JavaScript
considère l’expression 5=="5" comme vraie. Pour le JavaScript, le
chiffre 5 est égal à la chaîne de caractères contenant 5. Si ce
raccourci peut sembler acceptable, il y a des cas plus difficiles à
accepter :
var nb=5;
nb=="5"; // true
nb=="05.00"; // true
nb=="00005." // true

Tous ces tests d’égalité sont vrais. Le JavaScript applique une


conversion automatique et remplace les chaînes de caractères par
leur valeur numérique.
Pour s’assurer que la comparaison se fait bien sur des éléments de
même type, on utilise l’opérateur avec un signe égal triplé === :
var nb=5;
nb===5; // true
nb=="5"; // true
nb==="5"; // false

◆ Opérateurs de différence

La négation en JavaScript, et dans de nombreux autres langages,


est matérialisée par le caractère point d’exclamation !.
L’opérateur « différent de » s’écrit donc littéralement « n’est pas égal
à » avec != pour comparer uniquement le contenu et !== pour
comparer le contenu ou le type de données.

Certains langages, comme le SQL pour les bases de données,


utilisent la notation <> pour l’opérateur de différence. Cette
notation ne fonctionne pas en JavaScript.

Le caractère ! peut aussi être utilisé pour inverser une expression


booléenne. Il a dans ce cas la signification de « NOT », la négation :
if (!isMajeur) {
alert("Accès interdit");
// Redirection vers une autre page
}

Ici, on lit « si n’est pas majeur ». Le résultat serait équivalent avec


une syntaxe plus classique if (isMajeur==false).

◆ Autres opérateurs

Les autres opérateurs de comparaison sont plus classiques et plus


faciles à comprendre :
> signifie « strictement supérieur à ».
< signifie « strictement inférieur à ».
>= signifie « supérieur ou égal à ».
<= signifie « inférieur ou égal à ».

◆ La valeur null

La valeur null représente l’absence de valeur, c’est-à-dire le vide.


Il ne faut pas confondre null, false et undefined, qui ont des
significations différentes :

/* Egalités faibles */
console.log(null == false); // false
console.log(null == undefined); // true

/* Egalités strictes */
console.log(null === false); // false
console.log(null === undefined); // false

/* Affectations et types */
var monNull=null, monNullBis=null;
console.log(typeof monNull); // object
console.log(typeof inconnu); // undefined
console.log(monNull === monNullBis); // true

Il est à noter que, dans le cas d’une égalité faible, null et undefined
sont équivalents. Pourtant, il est possible d’affecter une variable
avec la valeur null.

Dans la plupart des langages, il existe des fonctions de test de la


valeur null, qui se comporte différemment d’une valeur classique.
En PHP et SQL, la fonction is_null() doit être utilisée.

3.5.2 Combinaison de conditions

Nous avons vu pour le moment des conditions simples. Il est bien


sûr possible de les combiner entre elles. Pour donner la priorité à
des conditions, il faut les encadrer de parenthèses. Les expressions
les plus profondes dans la hiérarchie des parenthèses sont
exécutées en premier.
Le test logique « ET » est matérialisé par le double signe du « et
commercial » &&.
Le test logique « OU » est matérialisé par le double signe du
« pipe » ||.
Voici quelques exemples de conditions combinées sur les variables
a et b :
Quelques exemples de tests combinés

Combinaisons de tests Explications


Si a vaut 1 ET b vaut
if ( (a==1) && (b==2) )
2
Si a est différent de 1
if ( (a!=1) && (b==2) )
ET b vaut 2
Si a vaut 1 OU b vaut
if ( (a==1) || (b==2) )
2
Si a différent de 1 OU
if ( (a!=1) || (b !=2) )
b différent de 2
Si a vaut 0
if ((a==0) || ( (a==1) && (b==2) )) OU
Si a vaut 1 et b vaut 2
Les combinaisons de tests sont vite difficiles à relire. Un
commentaire explicitant le but d’un test combiné n’est jamais inutile.

Trouver les caractères sur le clavier


Le & est accessible par la touche 1 au-dessus du caractère A
sur macOS ou sur Windows.
Le « pipe » | est moins facile à trouver. Sur un clavier macOS,
il est affiché par la combinaison [cmd] + [maj] + L. Sur un
clavier Windows, il faut utiliser la combinaison [Alt Gr] + 6 (au-
dessus du T).

3.5.3 Optimisation de l’écriture des conditions

À partir de deux ou trois combinaisons de tests, il faut se poser la


question de la façon d’écrire la condition pour la rendre la plus
logique et la plus lisible possible. La combinaison « OU » est la plus
délicate et souvent à l’origine d’erreurs de logique.
Parfois une inversion du sens simplifie la syntaxe. Reprenons
l’exemple du tableau ci-dessus :
if ( (a!=1) || (b!=2) ) { // Si a est différent de 1 OU b est différent de 2
// Actions A
} else {
// Actions B
}

Intellectuellement, cette condition est difficile à appréhender à cause


de l’opérateur « OU » combiné à des non-égalités. La condition est
vraie pour ces quelques exemples (a=1, b=0), (a=0, b=0) ou (a=0,
b=2). Elle sera fausse uniquement pour (a=1, b=2).
En algèbre booléen, la négation d’une expression consiste à
inverser les « ET » et les « OU », les égalités et les différences, et
les supérieurs et les inférieurs.
L’inversion de notre test, associée à l’inversion des actions A et B,
devient donc plus lisible et strictement identique sur le plan
fonctionnel :
if ( (a==1) && (b==2) ) {
// Actions B
} else {
// Actions A
}

_ 3.6 LES BOUCLES


Une boucle en programmation est un traitement répété plusieurs fois
grâce à un bloc d’instructions codé une seule fois. Il existe deux
types de structures de boucles :
La boucle for déclenche une boucle avec un compteur de
passage.
La boucle while répète la boucle tant qu’une condition est vraie.

3.6.1 La boucle for

La boucle for est idéale pour traiter les éléments d’un ensemble dont
on connaît à l’avance le nombre d’occurrences. Elle est très utilisée
avec les tableaux de données pour effectuer une action sur chacun
de ses éléments.

◆ Définition de la boucle for

La boucle for possède cette structure :


for (initialisation compteur ; condition sur compteur ; incrément compteur) {
// Traitements répétés par la boucle
}

La boucle for attend trois instructions séparées par des points-


virgules. Il s’agit bien d’instructions au sens JavaScript et non de
paramètres d’une fonction séparés par une virgule.
La première instruction correspond à la déclaration et à l’initialisation
d’une variable de compteur destiné à stocker le nombre d’itérations,
c’est-à-dire d’exécutions, de la boucle.
La seconde contient une condition sur le compteur qui autorise la
boucle à se répéter tant qu’elle est vraie.
La dernière est l’opération d’incrémentation de la variable de
compteur, c’est-à-dire d’augmentation de valeur, effectuée à la fin de
chaque traitement de la boucle.
Pour calculer la somme des nombres de 1 à 10, une boucle est
parfaitement adaptée :
<h1 id="monH1"></h1>
<script type="text/javascript">
var somme=0; // Déclaration de la variable qui stocke la somme
for (var n=1; n<=10; n=n+1) {
somme=somme+n; // A chaque passage de la boucle, la somme est complétée
}
document.getElementById("monH1").innerHTML="Somme = "+somme;
</script>

La variable somme vaut 55, égale à l’opération attendue


1+2+3+4+5+6+7+8+9+10.
À l’issue de ce script, la variable n vaut 11 : après le passage de la
boucle où n vaut 10, l’instruction n=n+1 s’exécute. Comme le test
n<=10 n’est plus vrai, la boucle s’interrompt et le script reprend
après l’accolade fermante du bloc de la boucle pour afficher la
somme dans l’élément HTML monH1.

Notation compacte de l’incrémentation


En JavaScript, l’incrémentation classique d’une unité de type
n=n+1 est très souvent remplacée par l’écriture n++. Cette
notation compacte a aussi l’avantage d’être immédiatement
compréhensible. La notation n-- est équivalente à n=n-1.
Il existe aussi une notation compacte pour des
incrémentations de valeurs différentes de 1. Ainsi a=a+10 se
note a+=10 et a=a-10 se note a-=10.
La boucle de notre exemple de calcul de somme est
habituellement écrite avec la forme compacte des
incrémentations :
for (var n=1; n<=10; n++) { somme+=n; }

◆ Interrompre la boucle avant la fin prévue

Il est possible d’arrêter une boucle for avant la fin prévue par le test
de condition en utilisant l’instruction break. La boucle s’interrompt et
le script se poursuit après le bloc d’instructions for { … }.
Même si cet usage est peu fréquent, il peut se révéler utile pour
accélérer des traitements en arrêtant une boucle, si le résultat est
déjà trouvé par exemple. Nous verrons d’autres exemples de
boucles for dans le chapitre sur les tableaux de données à partir
d’ici.

3.6.2 Les boucles sans fin

Si le test de condition d’une boucle ne change jamais d’état et reste


vrai tout le temps, la boucle s’exécute indéfiniment. Elle devient alors
une redoutable boucle sans fin. Le processeur est fortement
sollicité par ce traitement et la navigation est parfois impossible
pendant ce laps de temps. Même l’ordinateur de l’utilisateur peut
être fortement ralenti. Les navigateurs ont donc intégré une
protection contre les erreurs de programmation en détectant les
scripts trop longs et ils affichent à l’utilisateur un message
d’information lui permettant de forcer l’arrêt du script :

Message du navigateur détectant une boucle sans fin

En cliquant sur le bouton Quitter la page, vous stoppez l’exécution


du script.

Les boucles sans fin sont bien gérées par les navigateurs, qui
permettent à l’utilisateur de reprendre la main en quelques
secondes. Mais elles représentent un vrai danger dans d’autres
langages. Sur un serveur avec un disque dur SSD, une boucle
sans fin qui ajoute une entrée dans une base de données ou dans
un fichier texte peut remplir complètement un disque dur en
quelques secondes seulement. Dans ce cas, le serveur ne
répondra plus et il faudra probablement passer par un
administrateur ou l’infogérance pour purger le disque.
3.6.3 Les boucles conditionnées

Il existe un autre type de boucle qui n’utilise pas de compteur pour


déterminer quand s’interrompre. Imaginons que nous cherchons à
déterminer la valeur de n telle que la somme de 1 à n soit supérieure
à 1 000. Nous ne savons pas à l’avance combien d’itérations seront
nécessaires. Mais nous avons la condition de sortie évidente pour la
boucle de calcul de la variable somme.

◆ La boucle « tant que »

Une boucle while (« tant que ») est exécutée tant que sa condition
est vraie. Elle est définie par cette structure :
while (condition) {
// Traitements de la boucle
}

Si, dès le départ, la condition n’est pas vraie, le script n’entre pas du
tout dans la boucle.
Écrivons notre script de recherche de la valeur de n telle que la
somme de 1 à n vaille plus de 1 000 :
<h1 id="monH1"></h1>
<script type="text/javascript">
var n=0, somme=0; // Déclaration de la variable qui stocke la somme
while (somme<1000) {
n++; // n est incrémenté de 1
somme+=n; // A chaque passage de la boucle, la somme est complétée
}
document.getElementById("monH1").innerHTML="Somme = "+somme+" pour n =
"+n;
</script>

On commence par déclarer et initialiser les variables de travail de


notre script.
La boucle while est exécutée tant que la somme n’atteint pas la
valeur limite. À chaque itération de la boucle, la variable n est
incrémentée et la somme calculée. La boucle while teste alors la
nouvelle valeur de somme et s’interrompt quand elle dépasse la
valeur 1 000.
On obtient à la sortie de la boucle la valeur de n et le script affiche :

Somme = 1035 pour n = 45

Une bonne habitude en programmation est de contrôler le


fonctionnement de son script et les résultats obtenus. Si nous
reprenons le script précédent utilisant la boucle for et que nous
changeons la valeur maximale de la boucle par 45, le résultat
confirme que le script est conforme.
Pour éviter de tomber sur des cas particuliers, il peut être
pertinent de faire ce contrôle sur d’autres valeurs de n et de
somme.

◆ La boucle « fait tant que »

Il existe une autre boucle de type while, dont la condition s’effectue


après un premier passage systématique dans le bloc d’instructions.
Sa syntaxe est :
do {
// Instructions
} while (condition) ;

Notre script de recherche de valeur de n pourrait aussi s’écrire :


var n=0, somme=0; // Déclaration de la variable qui stocke la somme
do {
n++; // n est incrémenté de 1
somme+=n; // A chaque passage de la boucle, la somme est complétée
} while (somme<1000);

Même si cette structure est peu employée, il est utile de la connaître.


Cet exemple montre également qu’il n’existe jamais une seule
solution en programmation. Il en existe souvent de nombreuses, plus
ou moins élégantes, plus ou moins logiques, plus ou moins
compactes. L’essentiel est que le script soit lisible et
compréhensible.

Le JavaScript est rarement utilisé pour réaliser des traitements


lourds qui nécessitent des temps de traitements longs. Ici, nous
aborderons les Web Workers, qui permettent de lancer des
actions lourdes et longues sans que la navigation en pâtisse.

_ 3.7 LES FONCTIONS


Une fonction est un ensemble de traitements réutilisables effectués
à partir de paramètres et retournant un résultat.

3.7.1 Les fonctions natives

◆ La fonction de conversion d’une chaîne en nombre entier

Le JavaScript possède quelques fonctions natives qui vont nous


servir d’exemple d’introduction à la notion de fonction. La fonction
parseInt() convertit une chaîne de caractères en un nombre entier.
Son nom est très parlant. Il est composé du verbe parse
(« analyser ») et de Int pour Integer (« nombre entier »).
La fonction parseInt() attend jusqu’à deux paramètres :
chaine, paramètre obligatoire de type string qui sera analysé et
converti en entier.
base, paramètre facultatif de type number qui définit la base de
conversion. Par défaut, la base sera décimale.
La fonction parseInt() retourne le nombre entier correspondant à la
chaîne analysée ou la valeur NaN, pour Not a Number, si la chaîne
n’est pas convertible en nombre.
Voici quelques résultats d’appel à la fonction parseInt() :

console.log(parseInt("150")); // retourne 150


console.log(parseInt("150.45")); // retourne 150
console.log(parseInt("150xxx")); // retourne 150
console.log(parseInt("xxx150")); // retourne NaN
console.log(parseInt("FF", 16)) ; // retourne 255 = FF en hexadécimal

Nous verrons dans les deux prochains chapitres la manipulation des


chaînes de caractères et des nombres en JavaScript et le
fonctionnement des conversions.

◆ Description d’une fonction

La convention d’écriture pour décrire une fonction, dans tous les


langages, est la suivante :
typeRetour nomFonction(typeP1 param1, typeP2 param2 [,typePF paramFacultatif])

typeRetour est le type de donnée que la fonction retourne.


typeP1 à typePN sont les types de données pour les
paramètres de la fonction, ajoutés entre parenthèses et
séparés par une virgule.
Un paramètre entre crochets. Les crochets qui encadrent un
paramètre indiquent qu’il n’est pas obligatoire. Dans le cas d’un
paramètre facultatif, une valeur par défaut est appliquée,
généralement la plus logique ou la plus courante.
Ainsi pour parseInt(), la documentation présente la fonction avec :
Integer parseInt(String chaine [, String base])

En une ligne, on voit ce que la fonction retourne et les paramètres


attendus.
Notez que le JavaScript ne fait pas de distinction entre les types
Integer, Float et number. La distinction est destinée au
développeur, qui aura une vision plus précise de l’utilité de la
fonction.

3.7.2 Les fonctions personnalisées

Le JavaScript permet au développeur de créer ses propres


fonctions, selon ses besoins. L’intérêt d’une fonction est qu’elle n’est
codée qu’une seule fois et peut être utilisée ensuite partout dans un
ou plusieurs projets, et même partagée sur Internet avec d’autres
développeurs. Une modification, correction ou amélioration dans une
fonction est réalisée une seule fois et immédiatement appliquée lors
de tous les appels.
Une fonction personnalisée doit être correctement nommée selon les
règles de nommage définies. Il faut qu’elle soit facile à utiliser, avec
un nom cohérent, des paramètres utiles, placés dans un ordre
cohérent, et un fonctionnement sans faille.

◆ Les syntaxes pour déclarer une fonction

Il existe plusieurs façons de créer une fonction. La plus courante


utilise l’instruction function sous la forme :
function nomFonction(param1, … paramN) {
// Instruction de la fonction
}

La fonction peut aussi se créer avec le constructeur function() :


nomFonction=function(param1, … paramN) {
// Instruction de la fonction
}

Dans les deux cas, nomFonction() est une fonction qui peut être
appelée dans le reste du script.
Une fonction qui ne possède pas de paramètre est déclarée avec les
parenthèses vides.

Pour matérialiser clairement que nomFonction est bien une


fonction, on ajoute les parenthèses (qui sont destinées à recevoir
les paramètres), à son nom et on écrit donc nomFonction().

◆ Retourner un résultat

Pour retourner un résultat à la fonction, on utilise l’instruction return


suivie de la valeur. Cette instruction, dont le nom est explicite,
retourne non seulement la valeur mais stoppe l’exécution de la
fonction.
Notez que return peut aussi être utilisée sans valeur de retour. Dans
ce cas, la fonction se termine simplement.
Une fonction peut aussi se terminer sans aucune utilisation de
l’instruction return quand l’enchaînement des traitements parvient à
l’accolade fermante de la fonction.

◆ Utiliser une fonction

Décrivons une fonction qui retourne si nécessaire le « s » du pluriel


selon la valeur du nombre nb passé en paramètre :
String getPluriel(Number nb)

La fonction s’appelle getPluriel() selon notre charte de nommage. Le


verbe get signifie « obtenir », « récupérer », et la partie Pluriel est
évidente. Le nom de notre fonction est donc facile à retenir et à
utiliser.
Elle est définie en une seule ligne de traitement grâce à l’opérateur
ternaire :
function getPluriel(nb) {
return nb>1 ? "s" : "" ;
}
Une fois définie, la fonction s’appelle facilement :
var prix=25, nbArticle=1;
console.log("Votre panier vaut " + prix + " euro" + getPluriel(prix));
console.log("Il contient " + nbArticle + " article" + getPluriel(nbArticle));

À l’exécution, la console contient très logiquement :

Votre panier vaut 25 euros


Il contient 1 article

Une fonction est souvent déclarée dans un fichier JavaScript


externe. Son intégration dans la page web avec la balise <script>
et l’attribut src permet d’utiliser la fonction facilement partout où
elle est nécessaire. Il est d’ailleurs courant d’avoir un fichier
commun à toutes les pages contenant les fonctionnalités de base
du site.

◆ Gérer les paramètres facultatifs

Cette fonction est pour le moment générique. Elle traite uniquement


le pluriel en ajoutant un « s » mais ne permet pas de gérer les cas
particuliers, pluriels en « x » par exemple ou mots invariables,
comme le mot « colis ».
Pour traiter les cas particuliers de pluriel, nous avons besoin d’un
second paramètre facultatif qui sera renseigné uniquement si le
pluriel attendu n’est pas un « s ». La description de la fonction
devient :
String getPluriel(Number nb [, String pluriel])

Et la définition de la fonction devient :


function getPluriel(nb, pluriel="s") {
return nb>1 ? pluriel : "" ;
}
Le second paramètre est prérempli avec la valeur par défaut qui
sera utilisée si le paramètre n’est pas présent lors de l’appel.
L’opérateur ternaire ne retourne plus le « s » du pluriel mais le
paramètre pluriel de la fonction.
Les appels précédents sont toujours valables, même avec un seul
paramètre. Pour les cas particuliers, il suffit d’ajouter en second
paramètre le caractère du pluriel attendu :
console.log("Vous recevrez " + nbColis + " colis" + getPluriel(nbColis, ""));

Ici, colis est invariable, le second paramètre contient donc une


chaîne vide "".

Cet exemple de fonction de pluriel peut sembler inutile. Il suffirait


d’écrire le pluriel ou le féminin entre parenthèses dans les libellés,
comme par exemple le classique et froid « cher(e) client(e) ». De
petits détails comme la gestion des pluriels ou du féminin
apportent pourtant une personnalisation et une réelle plus-value
pour les utilisateurs.

◆ Améliorer une fonction

L’écriture de notre fonction getPluriel() montre très vite qu’il existe


encore d’autres cas particuliers de pluriels où le mot doit être modifié
et pas seulement complété.
Même si l’utilisation du mot « cheval » est vraiment rare dans un
JavaScript, notre fonction n’est pas encore digne d’être publiée sur
Internet et rendue publique car elle ne remplit pas correctement son
rôle avec tous les mots.

En programmation, un script n’est jamais vraiment terminé. Il peut


toujours y avoir une amélioration à apporter ou un cas particulier
à corriger qui nécessite de retravailler une fonction.

3.7.3 Variables globales et locales


Revenons sur les variables dans ce paragraphe consacré aux
fonctions. Nous avons déjà vu la définition de portée des variables
de bloc d’instructions. La notion de portée s’applique aussi dans les
variables de fonction. Même pour les plus expérimentés, la question
des portées est parfois source d’erreurs de conception.
Une variable globale, déclarée en dehors d’une fonction, est visible
et manipulable dans l’ensemble du script d’une page.
Une variable locale, déclarée à l’intérieur du corps d’une fonction,
n’est visible que localement, à l’intérieur de cette fonction.
Grâce à la console, ce script suit les valeurs des deux variables
globales nb et msg, avant, pendant et après le lancement d’une
fonction qui tente de les modifier :
var nb=10, msg="Hello World";
console.log("nb après l'initialisation = " + nb)
console.log("msg après l'initialisation = " + msg);

function getMessage() {
var msg="Bonjour dans la fonction";
nb++;
console.log("nb dans la fonction = " + nb)
console.log("msg dans la fonction = " + msg);
}

// Appel de la fonction
getMessage();
console.log("nb après appel de la fonction = " + nb);
console.log("msg après appel de la fonction = " + msg);

La console contient ces six lignes :


nb après l'initialisation = 10
msg après l'initialisation = Hello World
nb dans la fonction = 11
msg dans la fonction = Bonjour dans la fonction
nb après appel de la fonction = 11
msg après appel de la fonction = Hello World

Les deux premières lignes affichent le contenu des variables juste


après leur déclaration.
Dans la fonction, nb est incrémenté de 1 et vaut donc 11. Une
variable locale msg est déclarée avec la chaîne « Bonjour dans la
fonction ».
Les deux dernières lignes affichent les valeurs de nos variables
globales après l’exécution de la fonction. La variable globale nb a
été modifiée par la fonction et vaut toujours 11. La variable globale
msg n’a pas été modifiée par la fonction car la fonction a traité sa
variable locale, nommée aussi msg, mais qui n’a aucun effet sur la
variable globale.

Précaution sur les traitements internes


à une fonction
Déclarez et utilisez toujours des variables locales pour les
traitements intermédiaires de vos fonctions (somme, cumul,
indice de boucle…). Vous éviterez ainsi de potentielles
interactions indésirables avec des variables globales du reste
de votre script.

3.7.4 Les paramètres de fonctions

Il faut également comprendre que les paramètres déclarés dans une


fonction sont aussi des variables locales. Modifier leur valeur dans le
traitement de la fonction n’a aucun impact sur les variables utilisées
pour l’appel à la fonction, même si les noms sont identiques :
var nb=10;
function calculNb(nb) {
nb=nb*2;
console.log("nb dans la fonction = " + nb);
}
console.log("nb avant appel de la fonction = " + nb);
calculNb(nb);
console.log("nb après appel de la fonction = " + nb);

La console contient ces trois lignes :

nb avant appel de la fonction = 10


nb dans la fonction = 20
nb après appel de la fonction = 10

La fonction calculNb() est appelée avec le paramètre 10. Dans la


fonction, nb vaut donc 10×2=20. À l’issue de l’exécution de la
fonction, le paramètre qui a été passé n’a pas été modifié par le
traitement et vaut encore 10.
Il est donc recommandé de choisir les noms des paramètres de
manière à limiter les risques de confusion entre l’utilisation de la
fonction et sa programmation.
L’ensemble des paramètres passés à une fonction est contenu dans
le tableau arguments[] accessible à l’intérieur de la fonction. Ce
tableau permet de traiter tous les paramètres sans avoir à les
déclarer à la création de la fonction. Imaginons la fonction
getMoyenne(), qui calcule la moyenne de tous les nombres passés
en paramètres :
function getMoyenne() {
console.log(arguments);
var somme=0;
for (var i=0; i<arguments.length; i++) {
// Boucle sur l'ensemble des éléments du tableau
somme=somme+arguments[i];
}
// La moyenne est la somme des éléments divisée par le nombre d'éléments
return somme/arguments.length;
}
console.log(getMoyenne(10,20,30,28)); // retourne 22

Nous verrons le fonctionnement des tableaux dans le chapitre dédié


à partir d’ici.

3.7.5 Les fonctions anonymes

Il n’est pas utile de nommer une fonction qui ne sera pas appelée et
réutilisée ailleurs dans le code JavaScript. On peut donc déclarer
des fonctions anonymes, littéralement « qui n’ont pas de nom ».

◆ Le minuteur setInterval()

Pour mieux comprendre l’intérêt des fonctions anonymes,


découvrons le minuteur setInterval(), qui déclenche l’exécution d’une
fonction à intervalle régulier.
Sa syntaxe traditionnelle est la suivante :
<h1 id="monH1"></h1>
<script type="text/javascript">
var nb=0;
function timer() {
nb++;
document.getElementById("monH1").innerHTML=nb;
}
setInterval("timer()", 250);
</script>

La fonction setInterval() attend deux paramètres :


Le nom de la fonction à exécuter à intervalle régulier.
Le délai en millisecondes de l’intervalle de répétition.
La fonction setInterval() retourne un objet pointant sur le minuteur
créé. La fonction clearInterval() est utilisée pour supprimer le
minuteur.
Notre fonction timer(), qui ne fait ici qu’incrémenter et afficher un
compteur dans l’élément HTML monH1, n’est utilisée et appelée que
pour ce minuteur.
Une fonction anonyme est parfaitement adaptée à cette situation et
regroupe en une seule instruction la création de la fonction à
exécuter et le déclenchement de la minuterie :
setInterval(function() {
nb++;
document.getElementById("monH1").innerHTML=nb;
}, 250);

Le premier paramètre est simplement remplacé par la définition de la


fonction anonyme.
Cette syntaxe de création de fonction est très souvent utilisée en
JavaScript. Elle a l’avantage de regrouper le code de la fonction à
exécuter à l’endroit où elle sera appelée.

◆ Les fonctions auto-exécutées

Le JavaScript a introduit une syntaxe pour exécuter une fonction


anonyme immédiatement après sa création :
(function(param1, …, paramN) {
// Traitements
})(p1, …, pN);

Cette syntaxe remplace l’écriture traditionnelle de déclaration de la


fonction puis de son appel :
function maFonction(param1, …, paramN) {
// Traitements
}
maFonction(p1, …, pN);
Elle est tout à fait adaptée quand on doit lancer un traitement une
seule fois dans la page. Nous verrons ici dans le chapitre sur
ECMAScript 6 un exemple d’usage de ce type d’appel par le service
de mesure d’audience Google Analytics.
4

Les chaînes de caractères


en JavaScript

Objectif
Nous avons déjà utilisé à de nombreuses reprises quelques
manipulations de chaînes de caractères au cours des
exemples des chapitres précédents.
Le JavaScript est parfaitement adapté pour réaliser des
traitements puissants afin d’optimiser l’affichage
d’informations et de contrôler des données et des saisies des
utilisateurs.

_ 4.1 DÉCLARATION D’UNE CHAÎNE


DE CARACTÈRES
En programmation, une chaîne de caractères (string en anglais)
est une succession ordonnée de caractères de texte. Une chaîne est
matérialisée par des séparateurs qui indiquent le début et la fin de la
chaîne. Les séparateurs ne font pas partie de la chaîne de
caractères. Une chaîne ne contenant aucun caractère est une
chaîne vide.
4.1.1 Déclarer une chaîne simple

Pour définir une chaîne, il faut utiliser les séparateurs prévus dans le
langage. En JavaScript, comme dans de nombreux autres langages,
deux séparateurs sont à notre disposition :
Le guillemet " est le plus courant.
L’apostrophe ' est aussi utilisée.
Voici quelques utilisations correctes de chaînes :
var msg="Hello World";
var txt='Hello World';
var str="";
console.log("Bonne lecture");

Une chaîne peut être affectée à une variable ou utilisée directement


dans une autre instruction.
Il faut naturellement utiliser le même séparateur au début et à la fin
de la chaîne.
La syntaxe suivante retournera une erreur car la déclaration de la
chaîne n’est pas correctement terminée :
var msg="Hello World';

Attention, les bons séparateurs sont bien les " et ' et non pas les
caractères d’imprimerie que les logiciels comme Word utilisent.
Les séparateurs « et ‘ ne fonctionneront pas et déclencheront une
erreur. Word n’est pas un bon éditeur de texte pour coder.

4.1.2 Utiliser l’alternance de séparateurs

L’existence de deux séparateurs n’est pas du tout inutile. Il est ainsi


possible de déclarer des chaînes contenant un des séparateurs
dans le texte en choisissant l’autre à la déclaration de la chaîne :
console.log("L'auteur m'a dit de continuer la lecture");
console.log('Il nous a dit "Continuez la lecture"');
La console affiche les deux chaînes avec l’apostrophe et les
guillemets de citation :

L'auteur m'a dit de continuer la lecture


Il nous a dit "Continuez la lecture"

4.1.3 Utiliser le caractère d’échappement

Parfois, la chaîne de caractères doit contenir les deux séparateurs.


Pour une chaîne de caractères contenant « Il m'a dit : "Continuez la
lecture" », il faut utiliser le caractère d’échappement, ou antislash
en anglais, \. Le caractère d’échappement empêche l’interprétation
normale du caractère suivant. Ainsi, \" est transformé en ". Le double
antislash \\ devient \ dans la chaîne.
Voici quelques exemples d’utilisation du caractère d’échappement :
console.log("Il m'a dit : \"Continuez la lecture\"");
console.log("Le caractère d'échappement est \"\\\"");

L’affichage dans la console contient :

Il m'a dit : "Continuez la lecture"


Le caractère d'échappement est "\"

Antislash sur le clavier


Sur un clavier Windows, l’antislash est affiché avec la
combinaison [Alt Gr] + 8 (au-dessus du U). Sur macOS, la
combinaison est [cmd] + [maj] + : (à droite de la touche
espace).

Ce caractère sera très utile dans le paragraphe de manipulation


avancée des chaînes avec les expressions régulières à partir d’ici.
4.1.4 Déclarer une chaîne sur plusieurs lignes

Depuis ECMAScript 6, un troisième séparateur ` d’identification de


chaînes est apparu. Il a le principal avantage d’autoriser simplement
les sauts de ligne à l’intérieur d’une chaîne. Comme un saut de ligne
signale normalement la fin d’une instruction, il a fallu introduire ce
nouveau séparateur :
var chaine=`Début de la chaîne
qui se poursuit sur la ligne 2
puis la ligne 3`;
console.log(chaine);

La chaîne est affichée dans la console sans erreur d’exécution :

Début de la chaîne
qui se poursuit sur la ligne 2
puis la ligne 3

Encore une fois, la coloration syntaxique de votre éditeur de code


vous évitera bien des erreurs de déclaration de chaîne.

4.1.5 Conversion de types

Une chaîne de caractères peut aussi être créée à partir de tout objet
JavaScript et de sa méthode de conversion vers une chaîne
toString(). Créons quelques variables JavaScript pour observer le
fonctionnement de la conversion :

var dt=new Date(); // Date du jour


var nb=10; // Un nombre
var tab=new Array(1,2,8,10); // Un tableau
console.log(dt.toString());
console.log(nb.toString());
console.log(tab.toString());
La console contient une représentation textuelle lisible de nos objets
grâce à la fonction de conversion vers une chaîne toString() :

Wed Mar 21 2018 11:22:02 GMT+0100 (CET)


10
1,2,8,10

Nous étudierons à partir d’ici les objets du langage JavaScript.

_ 4.2 CONCATÉNATION DE CHAÎNES


L’opération d’assemblage de deux morceaux de chaînes de
caractères en une nouvelle chaîne s’appelle la concaténation.
L’opérateur de concaténation est simplement le caractère +, proche
de l’addition pour les nombres dans son principe. Ainsi l’instruction
suivante :
var msg="Bonjour"+" "+" lecteur";

affecte la chaîne « Bonjour lecteur » dans la variable msg.


Le résultat d’une concaténation comprenant une chaîne est toujours
une autre chaîne, même si la concaténation contient aussi d’autres
objets comme des nombres :
var nb=1000;
var msg="Vous êtes le "+nb+"ème lecteur";
console.log(msg);

La concaténation d’un nombre avec une chaîne retourne une


nouvelle chaîne :

Vous êtes le 1000ème lecteur


Cette particularité de la concaténation permet de convertir un
nombre en chaîne en lui concaténant simplement une chaîne vide :
var nb=1000;
console.log(typeof nb); // number
nb=nb+"";
console.log(typeof nb); // string

Il est toutefois préférable d’utiliser la méthode toString(), vue ici, sur


la variable nb pour réaliser une conversion de type.
Comme pour les opérations d’incrémentation, le JavaScript accepte
le raccourci d’écriture +=. Ces deux instructions sont strictement
identiques :
txt=txt+"Z";
txt+="Z";

_ 4.3 L’OBJET STRING


Le JavaScript est un langage objet, c’est-à-dire que tout élément est
un objet possédant des propriétés et des fonctionnalités attachées.
Même une simple chaîne de caractères est un objet de type string.
Elle possède ainsi les propriétés et les méthodes issues de son
constructeur String().
L’accès aux propriétés ou aux méthodes (fonctions) de l’objet se
matérialise par la notation pointée :
La syntaxe monObjet.unePropriete accède à la propriété.
La syntaxe monObjet.uneMethode() exécute la méthode.
Il est possible d’utiliser la notation pointée directement sur la chaîne
brute sans passer par une variable. Ainsi, la notation suivante
retourne la longueur de la chaîne « Bonjour » :
"Bonjour".length // Retourne 7 (les séparateurs ne sont pas comptés)

4.3.1 Longueur d’une chaîne


La longueur d’une chaîne est contenue dans la propriété length :

console.log(msg.length); // 28 caractères dans msg


console.log("Bonjour".length); // 7 caractères
console.log("".length); // 0 pour une chaine vide

La propriété length décrit le contenu actuel de la chaîne. Elle n’est


donc pas modifiable par le code. On dit qu’elle est accessible en
lecture seule.

4.3.2 Modifier majuscules et minuscules

Les méthodes toUpperCase() et toLowerCase() modifient la casse,


c’est-à-dire la distinction majuscule et minuscule, de la chaîne sur
laquelle elles sont appliquées :
var msg="Hello World";
console.log(msg.toUpperCase()); // affiche HELLO WORLD
console.log(msg.toLowerCase()); // affiche hello world

L’usage est très simple. Les méthodes s’appliquent sur la chaîne et


retournent le résultat de modification de casse sur l’ensemble des
caractères.

4.3.3 Nettoyer une chaîne

La méthode trim() (« réduire » en français) nettoie la chaîne de ses


caractères inutiles en début et en fin en supprimant les espaces,
tabulations et retour chariot :
String chaine.trim()

Ainsi, l’appel à :
" mon texte ! ".trim()

retourne la chaîne nettoyée :


"mon texte !"
Notez que les espaces en double dans le texte ne sont pas modifiés
par trim().

4.3.4 Accéder à un caractère précis

Grâce à la propriété length, qui donne la longueur de la chaîne, il est


facile de faire une boucle sur chacun des caractères de la chaîne.
La méthode charAt(indice) retourne le caractère à la position indice.
La méthode charCodeAt(indice) retourne le code ASCII du
caractère à la position indice.
En JavaScript, comme dans de nombreux langages de
programmation, le premier élément d’un ensemble a comme indice
0. Le premier caractère est donc accessible avec chaine.charAt(0).
Le dernier caractère a donc comme indice chaine.length-1.

4.3.5 Rechercher dans une chaîne

L’objet String met à disposition deux méthodes de recherche de


caractères dans une chaîne.
La méthode lastIndexOf() retourne l’indice de la dernière sous-
chaîne trouvée dans chaine :
Integer chaine.lastIndexOf(String sousChaine)

La méthode indexOf() retourne l’indice de la sous-chaîne trouvée


dans chaine à partir d’un indice de début facultatif :
Integer chaine.indexOf(String sousChaine [, Integer debut])

Si les méthodes de recherche ne trouvent pas la sous-chaîne, elles


retournent la valeur -1.

4.3.6 Extraire une sous-chaîne

L’extraction d’une sous-chaîne consiste à découper la chaîne


d’origine à partir d’un indice de début et jusqu’à un indice de fin pour
en retourner cette portion.
Les objets String proposent deux méthodes d’extraction très
proches, dont la seule différence réside dans la détermination de
l’indice de fin :
La méthode substring() attend un second paramètre facultatif
correspondant à l’indice de fin de la sous-chaîne à retourner. Si
l’indice de fin n’est pas précisé, la sous-chaîne se poursuit
jusqu’à la fin de la chaîne d’origine :
String chaine.substring(Integer debut [, Integer fin])

La méthode substr() attend en second paramètre obligatoire la


longueur de la sous-chaîne à extraire :
String chaine.substr(Integer debut, Integer longueur)

En combinant la recherche d’indices et l’extraction de sous-chaînes,


on peut récupérer le nom de domaine d’une page web ou d’une
adresse mail :
/* Retourne le nom de domaine d'une page web ou d'une adresse mail */
function getDomaine(txt) {
if (txt.indexOf("@")>0) { // adresse mail
return txt.substring(txt.indexOf("@")+1);
} else if (txt.indexOf("://")>0) { // Page web
if (txt.indexOf("/", 8)>0) {
return txt.substring(txt.indexOf("://")+3, txt.indexOf("/", 8));
} else {
return txt.substring(txt.indexOf("://")+3);
}
} else { // aucun des deux, retourne vide
return "";
}
}
var page="http://www.toutjavascript.com/reference/ref-string.length.php";
var goog="https://www.google.fr";
var mail="olivier@toutjavascript.com";
console.log(getDomaine(page)); // Affiche www.toutjavascript.com
console.log(getDomaine(goog)); // Affiche www.google.fr
console.log(getDomaine(mail)); // Affiche toutjavascript.com
Le domaine d’une adresse mail commence après le caractère @ et
se poursuit jusqu’à la fin de la chaîne.
Le domaine d’une page web commence après les :// et se termine
après le / suivant ou à la fin de la chaîne s’il n’est pas trouvé.
Notez ici l’utilisation de la combinaison de conditions else if.

Rien ne garantit que le domaine retourné soit syntaxiquement


valide. Nous verrons un moyen plus précis de vérifier une chaîne
à partir d’ici.

_ 4.4 LE TRAITEMENT DES CARACTÈRES


SPÉCIAUX
Nous allons maintenant utiliser l’ensemble des fonctionnalités de
chaînes de caractères vues jusqu’à maintenant pour construire et
afficher la table de caractères ASCII. Le code ASCII d’un caractère
est un nombre de 0 à 255, c’est-à-dire une taille d’un octet, qui
correspond à un caractère d’une chaîne.
Le script suivant réalise une boucle de 0 à 255. Il crée la table
ASCII, composée des codes et des caractères leur correspondant,
dans la variable table et l’affiche dans l’élément HTML identifié ascii.
Les propriétés CSS, non reproduites sur cette page, permettent de
mettre en forme l’affichage, avec en particulier l’utilisation de
column-count pour réaliser un multicolonnage automatique :
var ascii="";
for (var i=0; i<256; i++) {
ascii+="<div class='indice'>"+i+"</div>";
ascii+="<div class='char'>"+String.fromCharCode(i)+"</div>";
ascii+="<div class='break'></div>";
}
document.getElementById("ascii").innerHTML=ascii;
Retrouvez le script complet à l’adresse http://tjs.ovh/ascii.
Notez que la méthode fromCharCode() s’applique sur le
constructeur String directement et non sur une chaîne de caractères.
Elle retourne une chaîne de caractères formée des codes ASCII
passés en paramètres :
String String.fromCharCode(String ascii1 [, … String asciiN])

Voici le rendu à l’écran de notre table ASCII :

En raison de la variété et du nombre des formats de caractères


internationaux, l’encodage des caractères se fait aujourd’hui sur
deux octets et permet de gérer une table UNICODE de 255×255
soit 65 025 caractères.

4.4.1 L’encodage des caractères spéciaux

Dans tous les langages, les caractères spéciaux présents dans les
chaînes posent des difficultés d’interprétation et des risques de perte
de données. Le JavaScript propose un couple de fonctions
d’encodage et de décodage des caractères spéciaux, et des
caractères à risque comme l’espace, les caractères accentués, les
signes + =, etc.
La fonction d’encodage encodeURIComponent() retourne la chaîne
passée en paramètre encodée. Elle remplace la fonction dépréciée
escape() :
String encodeURIComponent(String chaine)

La fonction decodeURIComponent() réalise l’opération inverse et


remplace la fonction dépréciée unescape() :
String decodeURIComponent(String chaine)

Voici quelques exemples de retour de chaînes encodées :


console.log(encodeURIComponent("Hello World"));
// Hello%20World
console.log(encodeURIComponent("Caractères \"spéciaux\" ?"));
// Caract%C3%A8res%20%22sp%C3%A9ciaux%22%20%3F

Ce type d’encodage est souvent utilisé pour envoyer les données du


navigateur vers le serveur, qui décodera les données reçues. Ce
passage par l’encodage garantit un transfert fiable et sans perte.

Fonctionnalité dépréciée
Une fonctionnalité dépréciée dans un langage signifie qu’elle
n’est plus maintenue et devient obsolète. Le risque de
continuer à utiliser une fonction dépréciée est de la voir
disparaître complètement et d’aboutir après quelques années
à un script en erreur. Un suivi des actualités du langage est
indispensable pour garantir un code durable.

_ 4.5 EXÉCUTER UNE CHAÎNE


JAVASCRIPT
Le JavaScript a la particularité de pouvoir contenir du code
JavaScript dans une chaîne et de l’exécuter directement. La fonction
native eval() « évalue », c’est-à-dire exécute, la chaîne JavaScript
passée en paramètre et retourne son résultat éventuel :
Object eval(String code)
Voici quelques exemples de l’action de eval() :
eval("var compteur=10+1");
eval("compteur++");
eval("document.getElementById('monH1').innerHTML=compteur");

Ces trois lignes définissent une variable compteur et l’affichent dans


l’élément HTML monH1.
Notez l’utilisation des deux séparateurs de chaînes de caractères
dans la troisième ligne du script.

Attention, la fonction eval() est dangereuse si elle est exécutée


sur du contenu saisi par un utilisateur malveillant qui pourrait
injecter du code destiné à dérober des données privées, comme
les mots de passe ou des cookies. De manière générale, toute
donnée saisie par un utilisateur doit être considérée comme
dangereuse et nécessite un contrôle avant d’être utilisée.

_ 4.6 MANIPULATIONS AVANCÉES


AVEC LES EXPRESSIONS
RÉGULIÈRES
Les expressions régulières sont des objets qui existent dans tous les
langages de programmation. Malgré leur puissance, ils sont mal
connus et peu maîtrisés par les développeurs, qui utilisent souvent
des bibliothèques d’expressions toutes faites. Leur maîtrise est
pourtant un véritable atout dans la manipulation des chaînes de
caractères et des fichiers texte.
Tous les logiciels d’éditeurs de texte reconnaissent ce formalisme et
facilitent la recherche et le remplacement massif de données dans
un fichier.
Le terme « expression régulière » est la traduction littérale de
l’anglais regular expression. L’adjectif regular peut aussi signifier
« rationnel ». Les termes « expressions régulières » et
« expressions rationnelles » désignent la même fonctionnalité.

4.6.1 Intérêt des expressions régulières

Les objets de type string ont des fonctions permettant de leur


associer une expression régulière pour réaliser des tâches avancées
de vérification de formatage, de recherche et de remplacement de
sous-chaînes.
On peut par exemple retrouver toutes les sous-chaînes ayant la
structure d’une date au format JJ/MM/AAAA, d’un numéro de
téléphone ou d’une adresse mail à l’intérieur d’un texte.
Les expressions régulières facilitent également le contrôle de saisie
des informations par l’utilisateur pour n’autoriser que les caractères
possibles dans un mail ou un pseudo. Les expressions régulières
peuvent aussi contrôler qu’un mot de passe a la bonne taille
minimale, contient bien au moins un chiffre, une majuscule et un
caractère spécial.

Un script réalisant les mêmes résultats, sans utiliser d’expression


régulière, comporterait des dizaines de lignes et nécessiterait un
travail fastidieux de programmation et de tests.

4.6.2 Création d’une expression régulière


en JavaScript

La manipulation des expressions régulières nécessite la construction


d’un objet JavaScript créé par le constructeur RegExp() :
var reg=new RegExp(String motif [, String option]);

Le paramètre motif est une chaîne de caractères contenant le


« programme » de l’expression. Le paramètre option est facultatif.
Le JavaScript autorise aussi la création des expressions par la
syntaxe :
var reg=/motif/option;

Les éléments motif et option sont les paramètres de l’expression.


Par exemple, les expressions reg1 et reg2 sont strictement
identiques. Elles testent la présence d’une chaîne de 4 chiffres :
var regAn1=new RegExp("[0-9]{4}");
var regAn2=/[0-9]{4}/;

En utilisant la fonction regAn1.toString() de conversion d’un objet en


chaîne, on constate que les deux contiennent :
"/[0-9]{4}/"

L’avantage de la première écriture est qu’elle attend une chaîne de


caractères en paramètre. Cette chaîne peut être construite
dynamiquement à l’exécution du script. La seconde écriture est fixée
par le développeur.

4.6.3 Définition des motifs

Le motif est le cœur de l’expression régulière. Il contient la structure


de la chaîne à trouver et d’éventuelles options de recherche.
En contrepartie de la puissance des expressions régulières, le motif
utilise un formalisme avec de nombreux caractères spéciaux, peu
lisible et rapidement indéchiffrable, même pour un développeur
entraîné. Un commentaire décrivant le rôle d’une expression
régulière doit être un réflexe !
Le tableau suivant liste les différents éléments du langage des motifs
de RegExp :
Motif Signification
Repérage début et fin de chaîne
^ Début de ligne ou de chaîne
$ Fin de ligne ou de chaîne
Identification des caractères
. N'importe quel caractère
Motif Signification
ab|cd Chaîne ab ou cd
Groupe de caractères : n'importe lequel
[abcd]
de ceux entre crochets
Groupe de caractères : n'importe lequel
[a-z]
de a à z
Groupe de caractères : n'importe lequel
[A-Z]
de A à Z
Groupe de caractères inverse : tous
[^0-9]
sauf ceux définis par les crochets
Ne s’utilise jamais seul, sert de
\
caractère d'échappement
\\ Caractère \
\t Tabulation
\n Saut de ligne
\r Saut de ligne
\r Saut de ligne
\f Saut de page pour l’impression
Comptage des caractères
Caractère précédent facultatif ou
*
présent un nombre illimité de fois
+ Caractère précédent au moins une fois
Caractère précédent présent une et
?
une seule fois ou absent
Caractère précédent présent
{n}
exactement n fois
Motif Signification
Caractère précédent présent au moins
{n,}
n fois
Caractère précédent présent entre n et
{n,m}
m fois
Raccourcis d’écriture
\d Chiffres (équivalent à [0-9])
\D Sauf chiffres (équivalent à [^0-9])
\b Frontière de mot (espace, alinéa…)
Caractère d’espacement (espace,
\s tabulation, saut de page, de ligne…)
équivalent à [ \f\n\r\t\v]
\S Un seul caractère sauf un espacement
N'importe quel caractère
\w alphanumérique, y compris underscore
(_), équivalent à [A-Za-z0-9_]
Tout sauf un caractère alphanumérique
\W
équivalent à [^A-Za-z0-9_]
Expression parenthésée (mémorisée
(x)
pour un remplacement)
Dans un motif, tous les caractères portent un sens. Il n’est pas
possible d’ajouter des espaces pour le rendre plus lisible.
L’objet de ce livre n’est pas de traiter les expressions régulières dans
le détail mais de vous en donner les bases pour que vous puissiez
progresser et les utiliser naturellement. Voici quelques motifs
d’expressions régulières expliqués.

◆ Motif pour un pseudonyme sur 3 à 20 caractères


^[0-9a-zA-Z._-]{3,20}$
Le comptage de 3 à 20 caractères se réalise simplement avec la
partie {3,20}. Le choix des caractères est défini dans la partie entre
crochets. Les caractères numériques, alphabétiques en minuscules
et majuscules, le point, le tiret et l’underscore sont autorisés. Tous
les autres caractères non présents dans les crochets sont interdits.
Les éléments ^ et $ matérialisent le début et la fin de chaîne.

◆ Motif de format date JJ/MM/AAAA


[0-9]{2}[-/]{1}[0-9]{2}[-/]{1}[0-9]{4}

Ce motif est séparé en trois parties. Le jour est un chiffre sur deux
caractères suivi d’un séparateur – ou / présent obligatoirement. La
partie du motif concernant le mois est identique. L’année est sur
quatre chiffres.
Nous pourrions être encore plus précis sur le motif. Le jour ne peut
pas commencer autrement que par 0, 1, 2 ou 3. Le mois ne peut
commencer que par 0 ou 1. Le nouveau motif s’écrit donc :
[0123][0-9]{1}[-/]{1}[01][0-9]{1}[-/]{1}[0-9]{4}

◆ Motif d’adresse mail

Le contrôle d’adresse mail est un cas typique d’usage des


expressions régulières :
[a-z0-9\-_]+[a-z0-9\.\-_]*@[a-z0-9\-_]{2,}\.[a-z\.\-_]+[a-z\-_]+

Grâce à l’option i (pour ignorer la casse), on peut simplifier


l’expression en éliminant tous les groupes de caractères A-Z du
motif.
Adresse mail insensible à la casse
Une croyance courante et ancrée veut qu’une adresse mail
soit sensible à la distinction majuscule/minuscule. Ce n’est
pas le cas. Un nom de domaine n’est pas non plus sensible à
la casse. Mais un nom de fichier, une adresse d’image ou de
page web le sont.

Le format d’une adresse mail est très encadré. Le motif


correspondant est donc assez long :
[a-z0-9\-_]+ : en début d’adresse, au moins un caractère parmi
les caractères alphanumériques, - et _
[a-z0-9\.\-_]* : autant de caractères que souhaité parmi les
caractères alphanumériques, ., - et _
@ : une fois arobase
[a-z0-9\-_]+ : au moins un caractère parmi les caractères
alphanumériques, ., - et _
\. : une seule fois le caractère .
[a-z0-9\.\-_]{2,} : au moins deux caractères parmi les caractères
alphanumériques, ., - et _
[a-z0-9]+ : au moins un caractère alphanumérique en fin
d’adresse

Le format possible d’une adresse mail a changé plusieurs fois au


cours des dernières années. Avec l’arrivée des domaines au
format international, les caractères accentués sont autorisés.
L’ouverture de centaines de nouvelles extensions autorise
maintenant des extensions allant jusqu’à huit caractères et sans
aucun doute ce sera davantage dans l’avenir (.attornay par
exemple). L’usage d’une fonction générique qui ne sera modifiée
qu’une seule fois pour suivre les actualités de l’Internet est
fortement recommandé.
◆ Motif d’une URL

Le motif d’une URL nous permettra de rendre les liens cliquables


dans un texte :
(ht|f)tps?:\/\/\S*

(ht|f) : une URL commence par http ou ftp.


s? : le « s », indiquant que l’URL est sécurisée, est facultatif.
:\/\ : représente la partie « :// » avec le caractère
d’échappement.
\S* : représente tous les caractères sauf les espacements et
séparateurs.

4.6.4 Définition des options du motif

Un motif d’expression régulière peut recevoir deux options,


matérialisées par les caractères i et g.
L’option i indique que le motif ignore la casse. L’option i est pratique
et courante pour éviter d’avoir à gérer le motif en majuscules et
minuscules.
L’option g indique que l’expression régulière doit être évaluée
globalement sur l’ensemble de la chaîne.
Les options s’ajoutent dans le second paramètre de la construction
de la RegExp :
var reg1=new RegExp("[0-9]{4}", "gi");
var reg2=/[0-9]{4}/gi;

4.6.5 Validation de format d’une chaîne

Maintenant que nous savons créer une expression régulière avec le


bon motif, utilisons-la pour réaliser nos traitements sur les chaînes
de caractères.
Les objets RegExp possèdent deux méthodes ayant en paramètre
une chaîne de caractères.
La méthode test() retourne vrai ou faux selon que chaine satisfait au
motif de reg :
Boolean reg.test(String chaine)

La méthode exec() retourne la première sous-chaîne de chaine


correspondant au motif de reg :
String reg.exex(String chaine)

Voici un exemple de retour sur les méthodes test() et exec() :


var regDate=new RegExp("[0123][0-9]{1}[-/]{1}[01][0-9]{1}[-/]{1}[0-9]{4}");
var txtDate="Johnny Hallyday est né le 15/06/1943 et mort le 05-12-2017";
console.log(regDate.test(txtDate)); // retourne true
console.log(regDate.exec(txtDate)); // retourne 15/06/1943

◆ Contrôler une chaîne au format email

Reprenons notre motif de recherche d’un email et utilisons-le pour


valider qu’une chaîne contient bien une adresse mail :
var r=/^[a-z0-9\-_]+[a-z0-9\.\-_]*@[a-z0-9\-_]{2,}\.[a-z\.\-_]+[a-z\-_]+$/i;
var mail1="contact@google.fr";
var mail2="contact.client1@france.google.com";
var mail3="contact client@google.fr";
var mail4="contact@google";
var mail5="mail: contact@google.fr";
var mail6="contact-client-75000@fr.mail-google.fr";
console.log(r.test(mail1)); // true
console.log(r.test(mail2)); // true
console.log(r.test(mail3)); // false (contient un espace)
console.log(r.test(mail4)); // false (pas d'extension au domaine)
console.log(r.test(mail5)); // false (commence par mail: )
console.log(r.test(mail6)); // true

4.6.6 Recherche et remplacement de motifs


Les objets String possèdent quatre méthodes ayant une expression
régulière en paramètre.
La méthode search() retourne le premier indice de caractère
correspondant à RegExp :
Integer chaine.search(RegExp reg)

La méthode replace() permet de trouver et de remplacer par une


chaîne de remplacement les occurrences répondant aux critères
d’une RegExp :
String chaine.replace(RegExp reg, String remplacement)

La méthode match() retourne dans un tableau toutes les


occurrences répondant aux critères d'une RegExp :
Boolean chaine.match(RegExp reg)

La méthode split() découpe une chaîne en un tableau à partir d’une


RegExp de motif représentant le séparateur :
Array chaine.split(RegExp separateur)

Attention, notez bien que certaines méthodes s’appliquent sur des


RegExp avec une chaîne en paramètre et que les autres
s’appliquent sur des chaînes avec une RegExp en paramètre.

◆ Extraire toutes les dates d’un texte

La méthode match() retourne un tableau de toutes les sous-chaînes


correspondant au motif de la RegExp.
var regDate=new RegExp("[0123][0-9]{1}[-/]{1}[01][0-9]{1}[-/]{1}[0-9]{4}", "g");
var txt="On connaît tous 11/11/1918, 08/05/1945, 18/06/1940, 14/07/1789";
console.log(txt.match(regDate));

Ce script affiche dans la console le tableau de données contenant


les quatre dates trouvées dans la chaîne txt :
(4) ["11/11/1918", "08/05/1945", "18/06/1940", "14/07/1789"]
0:"11/11/1918"
1:"08/05/1945"
2:"18/06/1940"
3:"14/07/1789"

◆ Remplacer des éléments dans une chaîne

La recherche d’éléments peut s’accompagner d’un remplacement


des sous-chaînes trouvées par les expressions parenthésées du
motif de l’expression régulière.
Ce petit script récupère le contenu de l’élément monDiv, le traite
pour rendre les URL cliquables avec replace() :
<div id="monDiv">
Vous trouverez sur http://www.toutjavascript.com un outil d'assistance à la création
d'expressions régulières à la page
http://www.toutjavascript.com/service/regexp.php !
</div>
<script type="text/javascript">
var txt=document.getElementById("monDiv").innerHTML;
var regUrl=/((ht|f)tps?:\/\/\S*)/gi;
txt=txt.replace(regUrl, '<a href="$1">$1</a>');
document.getElementById("monDiv").innerHTML=txt;
</script>

Retrouvez ce script qui rend les URL cliquables à l’adresse


http://tjs.ovh/url.
L’expression régulière de reconnaissance de motif d’URL a été
placée entre parenthèses. L’ensemble de l’expression parenthésée
est mémorisé à l’exécution de la méthode replace() et récupérable
dans le second paramètre de remplacement par la variable $1. Les
expressions parenthésées sont mémorisées dans l’ordre des
parenthèses dans les variables de $1 à $9.
Le résultat du remplacement est replacé dans monDiv qui affiche
ainsi les liens cliquables.

◆ Visualiser une vidéo YouTube à partir de son URL

Nous utilisons ce principe de remplacement par expression régulière


pour détecter les URL de vidéos YouTube au format
https://youtu.be/IdentifiantVideo et les remplacer par la
visionneuse. La fonction de remplacement est :
function formaterTexteYoutube(t) {
var regYoutube=new RegExp("(https://youtu.be/)([0-9a-zA-Z]+)(\\s*)", "gi");;
return t.replace(regYoutube, "<div class=\"youtube\"><iframe width=\"560\"
height=\"315\" src=\"https://www.youtube.com/embed/$2\" frameborder=\"0\"
allow=\"autoplay; encrypted-media\" allowfullscreen></iframe></div>");
}

Un exemple complet est visible à l’adresse http://tjs.ovh/youtube.

◆ Former un tableau de prénoms depuis une liste

La méthode split() attend en paramètre une RegExp désignant le


séparateur des éléments et retourne un tableau des éléments.
Imaginons que nous ayons une liste de prénoms séparés par
différents séparateurs comme la virgule, l’espace ou le point-virgule.
Grâce à ce script, nous pouvons les retrouver dans un tableau :
var prenom="Jean, Robert ; Gérard, Louis, Arthur, Jean-Luc";
var regSep=/[ ,;]+/gi;
var prenoms=prenom.split(regSep);
console.log(prenoms);

Le tableau prenoms (notez le « s » au nom de la variable pour


indiquer qu’il s’agit d’un tableau contenant des prénoms) contient les
six prénoms. Ils sont affichés dans la console sous forme de chaîne
avec toString() :
Jean,Robert,Gérard,Louis,Arthur,Jean-Luc

4.6.7 L’importance des options

L’option i pour ignorer la distinction de casse dans une expression


régulière est facile à comprendre.
L’option g est plus délicate à utiliser. Elle force le traitement à
s’exécuter sur l’ensemble de la chaîne de caractères. Sans l’option
g, le traitement s’arrête à la première occurrence trouvée.
Par exemple, si on recherche les années dans une chaîne de
caractères, l’option g est cruciale :
var regAnnee1=/[0-9]{4}/; // Sans option g
var regAnnee2=/[0-9]{4}/g; // Avec option g
var txt="On connaît tous : 18/11/1918, 08/05/1945, 18/06/1940, 14/07/1789";
console.log(txt.match(regAnnee1).toString()); // retourne 1918
console.log(txt.match(regAnnee2).toString()); // retourne 1918,1945,1940,1789

Notez ici l’utilisation de toString() pour transformer les tableaux


retournés par match() en chaîne de caractères lisible.
5

Les mathématiques

Objectif
Le JavaScript est à l’aise avec les traitements mathématiques
de calculs classiques de type monétaire ou statistique. Il est
aussi capable de réaliser des traitements avec des fonctions
trigonométriques.
Dans ce chapitre, nous allons parcourir l’ensemble des
possibilités mathématiques du langage.

_ 5.1 LES CONVERSIONS DE TYPES


DE DONNÉES

5.1.1 Convertir une chaîne en nombre

Nous avons vu au début du chapitre sur les chaînes de caractères


comment transformer le type number en type string, avec la
méthode bien nommée toString(). Le JavaScript propose deux
fonctions pour réaliser l’opération inverse.
parseInt() analyse la chaîne en paramètre pour retourner un nombre
entier. Cette fonction a déjà été détaillée dans l’introduction aux
fonctions JavaScript.
Integer parseInt(String chaine [, Integer base])
parseFloat() retourne un nombre à virgule (type flottant, ou float en
anglais) à partir de la chaîne en entrée.
Float parseFloat(String chaine)

Le fonctionnement de ces fonctions d’analyse peut être un peu


déroutant. La fonction s’interrompt dès que le caractère en cours
d’analyse ne peut pas former un nombre avec les caractères
précédents.
Si aucun nombre n’est trouvé par l’analyse, les méthodes retournent
NaN (Not a Number).
Voici quelques exemples de retours de conversion :
console.log(parseInt("10")); // Retourne 10
console.log(parseInt("10ZZ")); // Retourne 10 (ZZ est ignoré)
console.log(parseInt("a")); // Retourne NaN
console.log(parseFloat("10.32")); // Retourne 10.32
console.log(parseFloat("1.3e2")); // Retourne 130 au format scientifique 1.3e2

5.1.2 Les règles de priorité de calcul

Il n’est pas inutile de rappeler les règles de priorité usuelles des


opérations mathématiques. Le JavaScript respecte les règles
mathématiques universelles suivantes :
Priorité aux opérations entre parenthèses les plus profondes.
Priorité aux multiplications et divisions sur additions et
soustractions.
L’opération « 2 + 5 × 3 » revient à « 2 + 15 », soit un résultat de 17.
L’opération « (2 + 5) × 3 » revient à « 7 × 3 », soit un résultat de 21.

5.1.3 Les nombres particuliers

◆ Les nombres en notation scientifique

Le JavaScript interprète correctement le format des nombres en


notation scientifique exprimé sous la forme :
a × 10n

Cette notation se lit « a multiplié par 10 à la puissance n », avec a un


nombre à virgule positif ou négatif et n un entier positif ou négatif. En
JavaScript, la notation devient :
aen

Voici quelques exemples de nombres en notation scientifique et leur


valeur en format décimal :
1,25e5 vaut 125 000.
–5,5e–3 vaut –0,0055.
8e6 vaut 8 000 000.

◆ Not a Number

Les opérations de conversion ou de calcul impossible retournent la


valeur NaN. Ainsi, parseInt("AZERTY") ou le calcul de la racine
carrée d’un nombre négatif retournent toujours NaN.

Malgré son acronyme signifiant que le résultat n’est pas un


nombre, NaN est bien de type number en JavaScript.

◆ Infinity

Le JavaScript identifie les opérations qui tendent vers l’infini positif


ou négatif et retourne la valeur Infinity ou -Infinity. Ainsi, le calcul de
2/0 retourne Infinity et le logarithme de 0 vaut -Infinity.
Le nombre Infinity est également de type number.

5.1.4 Le type number

Le JavaScript est un langage faiblement typé. Tous les nombres sont


de type number, qu’ils soient des entiers petits ou très grands ou des
nombres à virgule. Les types Integer ou Float n’existent pas.
Ainsi, tous ces nombres sont de type number :
console.log(typeof 5.3);
console.log(typeof -2);
console.log(typeof 10e100);
console.log(typeof NaN);
console.log(typeof (1/0));

Les calculs à virgule flottante


Le modèle de stockage des nombres en mémoire entraîne
parfois des erreurs d’arrondis. Il arrive que le résultat d’un
calcul soit du type 2.999999999 au lieu de l’entier précis 3.
Ce phénomène se produit dans tous les langages. Il implique
d’afficher le résultat avec un traitement préalable, en utilisant
par exemple l’objet Intl détaillé.

5.1.5 Le reste d’une division

La syntaxe qui retourne le reste d’une division par le nombre n est


%n. Voici quelques exemples de résultats :
9%3 retourne 0 car 9 est divisible par 3.
10%3 retourne 1 car 10 vaut 3×3 + 1.
8%2 retourne 0 car 8 est pair.

5.1.6 Les nombres après la virgule

Les nombres possèdent la méthode toFixed(), qui permet de les


formater avec un nombre de décimales fixé :
String nombre.toFixed(Interger nbDecimal)

Nous pouvons utiliser cette méthode pour formater les tailles de


fichier, en octets, en nombres plus faciles à lire par un humain :
function getHumanSize(s) {
s=parseInt(s); /* Pour s'assurer que le paramètre d'entrée est entier */
if (s<1024) {
return s+" o";
} else if (s<1024*1024) {
return (s/1024).toFixed(1)+" ko";
} else if (s<1024*1024*1024) {
return (s/1024/1024).toFixed(1)+" Mo";
} else {
return (s/1024/1024/1024).toFixed(1)+" Go";
}
}

Notez qu’en informatique le changement de millier ne se fait pas à


chaque multiple de 1 000 comme en mathématiques, mais pour
les multiples de 1 024, c’est-à-dire 2 à la puissance 10, soit
Math.pow(2, 10), la valeur la plus proche de 1 000 pour une
puissance de 2.

Et voici quelques résultats :

console.log(getHumanSize(1050)); // 1.0 ko
console.log(getHumanSize(400000)); // 390.6 ko
console.log(getHumanSize(5000000)); // 4.8 Mo

Nous utiliserons de nouveau cette fonction dans le chapitre sur


l’upload de fichier.

_ 5.2 L’OBJET MATH


L’objet Math est le support de toutes les fonctions mathématiques du
langage JavaScript. L’objet propose des constantes et des fonctions
que nous allons lister dans ce paragraphe.
5.2.1 Les constantes de Math

L’objet Math possède des propriétés qui sont des constantes


remarquables en mathématiques. La plus utile est la constante π,
accessible avec Math.PI. Voici la liste des constantes disponibles :
Constante Signification Valeur
Math.PI Valeur de π 3,141592653589793
Valeur de e tel que
Math.E 2,718281828459045
ln(e)==1
Logarithme népérien (ou 0,693147180559945
Math.LN2
naturel) de 2 3
Logarithme népérien (ou
Math.LN10 2,302585092994046
naturel) de 10
1,442695040888963
Math.LOG2E Logarithme en base 2 de e
4
0,434294481903251
Math.LOG10E Logarithme de e
8
Math.SQRT1_ 0,707106781186547
Racine carrée de ½
2 6
1,414213562373095
Math.SQRT2 Racine carrée de 2
1
Les constantes de l’objet Math sont toutes écrites en majuscules,
comme le sont les constantes habituellement dans d’autres
langages.

Notez qu’il est indispensable de passer par l’objet Math pour


accéder à une constante. La constante PI n’existe pas. La bonne
syntaxe est bien Math.PI. De la même manière, la fonction cos()
n’existe pas. La bonne syntaxe est toujours Math.cos().
5.2.2 Les fonctions arithmétiques

Les fonctions arithmétiques permettent d’arrondir un nombre, de


trouver son signe ou le plus grand et le plus petit nombre parmi une
série de nombres :
Méthode Signification
Retourne la valeur absolue de
Math.abs(a)
a
Retourne le signe de a (soit -1
Math.sign(a)
si a<0, 0 si a==0, 1 si a>0)
Retourne l’entier arrondi
Math.ceil(a)
supérieur de a
Retourne l’entier arrondi
Math.floor(a)
inférieur de a
Retourne l’entier arrondi le plus
Math.round(a)
proche de a
Retourne le plus petit des
Math.min(a, … z)
paramètres
Retourne le plus grand des
Math.max(a, … z)
paramètres

5.2.3 Les fonctions de base

Le JavaScript propose évidemment les fonctions mathématiques de


base :
Méthode Signification
Retourne le nombre a à la
Math.pow(a, p)
puissance p
Math.exp(a) Retourne l’exponentielle de a
Math.sqrt(a) Retourne la racine carrée de a
Math.log(a) Retourne le logarithme de a

La console du navigateur est un outil très utile pour tester des


opérations courantes de l’objet Math. Grâce à l’auto-complétion,
vous obtenez de plus la syntaxe exacte des fonctionnalités.

5.2.4 Les fonctions trigonométriques

Et bien sûr, nous retrouvons les fonctions trigonométriques


habituelles :
Méthode Signification
Math.cos(a) Retourne le cosinus de a
Math.sin(a) Retourne le sinus de a
Math.tan(a) Retourne la tangente de a
Math.acos(a) Retourne l’arccosinus de a
Math.asin(a) Retourne l’arcsinus de a
Math.atan(a) Retourne l’arctangente de a
Nous les utiliserons au chapitre sur les objets Canvas.

_ 5.3 LES NOMBRES ALÉATOIRES


Le JavaScript propose également la méthode random()
(« aléatoire » en français), qui retourne un nombre aléatoire à virgule
supérieur ou égal à 0 et strictement inférieur à 1 :
Float Math.random()

La méthode random() n’accepte pas de paramètre pour définir


l’intervalle des possibilités du nombre aléatoire retourné, qui reste
toujours entre 0 et 1.
Pour réaliser l’équivalent d’un tirage de dé, il faut créer notre propre
fonction getTirageDe() avec en paramètre le nombre de faces du
dé :
function getTirageDe(n) {
return Math.floor(n * Math.random() + 1);
}

Le paramètre n est le nombre de faces du dé. La fonction retourne


l’arrondi inférieur d’un nombre aléatoire, multiplié par le nombre de
faces, plus 1.
En informatique, un tirage n’est jamais purement aléatoire. Le tirage
est pseudo-aléatoire, car il s’appuie sur l’horloge du système. Pour
vérifier que notre fonction de tirage de dé est équitable, nous allons
réaliser un très grand nombre de tirages grâce à une boucle et nous
allons compter le nombre de fois que nous obtenons chaque valeur.
La loi des grands nombres nous indique qu’une fonction aléatoire
équitable nous retourne une proportion égale pour chaque valeur
possible du tirage.
Nous réalisons une boucle de six millions d’itérations sur un tirage
de dé à six faces. Pour chaque itération, nous incrémentons un
compteur pour le résultat du tirage trouvé.
Le script correspondant est donc :
console.time("boucle");
var resultats=new Array(0,0,0,0,0,0,0);
var nbTirage=6e6;
for (var i=0; i<nbTirage; i++) {
var de=getTirageDe(6);
resultats[de]++;
}
console.timeEnd("boucle");
console.log(resultats);

Vous pouvez trouver le script de vérification de notre fonction de


tirage de dé à l’adresse http://tjs.ovh/random.
À la fin du script, la console du navigateur contient :
boucle: 111.5732421875ms
(7) [0, 1001171, 999207, 998725, 1000903, 999841, 1000153]
0:0
1:1001171
2:999207
3:998725
4:1000903
5:999841
6:1000153

Le tableau resultats contient les cumuls de chaque tirage. Comme


prévu, le tirage 0 ne s’est jamais produit. Les tirages des faces 1 à 6
semblent équilibrés autour d’un million de tirages, avec seulement
quelques centaines de tirages d’écart entre chaque face.
Bien sûr, comme le script utilise des nombres aléatoires, chaque
exécution sera différente.

5.3.1 Mesure de performance d’un script

Ce script est l’occasion d’aborder les mesures de performances


d’un script. Vous avez noté les lignes console.time("boucle") et
console.timeEnd("boucle") et l’affichage dans la console du libellé
boucle: suivi d’une durée en millisecondes.
La méthode time(nom) de console initialise un chronomètre de libellé
nom.
La méthode timeEnd(nom) bloque le chronomètre nom et affiche
dans la console le temps passé en millisecondes (ms).
Sur mon MacBook de travail, la boucle de six millions de tirages n’a
pris que 111 millisecondes, soit un temps à peine perceptible pour
un humain. Naturellement, sur un smartphone, le délai serait bien
plus long et le script serait sans doute trop lent pour un rendu en
temps réel avec un si grand nombre d’itérations.
_ 5.4 L’AFFICHAGE FORMATÉ
DE NOMBRES
Avec ECMAScript 6, le JavaScript propose enfin des fonctionnalités
avancées de formatage de nombres et de montants dans les
différents formats internationaux. Un nouvel objet Intl, abréviation
d’« international », permet de définir des mises en forme et d’afficher
des nombres dans un format lisible. Le même objet Intl possède
également des fonctions d’affichage de dates dans différents formats
internationaux que nous détaillerons ici.
L’objet Intl possède le constructeur NumberFormat, qui définit le
format du nombre à utiliser :
Object Intl.NumberFormat([String local, JSON options])

La fonction NumberFormat() crée l’objet qui servira à formater des


nombres. Elle peut recevoir deux paramètres facultatifs définissant
le format :
local est une chaîne de langue de type "fr-FR" ou "en-US". Par
défaut, le format local du navigateur est utilisé.
options est un objet au format JSON (voir le chapitre dédié à ce
format) qui permet de compléter le format défini par la chaîne
local. Parmi les propriétés des options, notons :
• style, qui peut valoir decimal (par défaut), currency, percent ;
• currency, qui contient le code de monnaie (ex. : EUR, USD) ;
• currencyDisplay, qui indique le mode d’affichage de la
monnaie ;
• Code, qui affiche le code monnaie ;
• maximumFractionDigits, qui contient le nombre de chiffres
après la virgule.
L’objet ainsi construit possède une seule méthode format(), qui
attend en paramètre le nombre à retourner sous forme de chaîne de
caractères dans le format défini.
Voici quelques exemples de formatage de nombres :
// Format par défaut de l'appareil
var int1=new Intl.NumberFormat();
console.log(int1.format(8560.154));
console.log(int1.format(123456789.154));
// Format français de montants en euros
var int2=new Intl.NumberFormat("fr-FR", {style: "currency", currency: "EUR",
currencyDisplay: "symbol"});
console.log(int2.format(5000));
console.log(int2.format(14.99));
// Format américain de montants en $
var int2=new Intl.NumberFormat("en-US", {style: "currency", currency: "USD",
currencyDisplay: "symbol"});
console.log(int2.format(50000));
console.log(int2.format(1400.99));

L’affichage dans la console est de ce type :

8 560,154
123 456 789,154
5 000,00 €
14,99 €
$50,000.00
$1,400.99

Dans le format français, le séparateur de milliers est l’espace, le


séparateur décimal est la virgule et la devise s’affiche après un
montant. Aux États-Unis, tout est inversé ! Le séparateur de milliers
est une virgule, les décimales sont placées après le point et les
unités de devises sont placées avant le montant.

Notez qu’en programmation, le séparateur décimal est toujours le


point.
L’écriture var Pi=3,14 générera donc une erreur à l’exécution.
L’objet Intl est très utile pour les sites à vocation internationale. Le
formatage des données peut être déporté sur le poste de l’utilisateur,
directement dans le format défini par son navigateur. Le JavaScript
connaît tous les formats internationaux et garantit que l’utilisateur
comprendra parfaitement l’information.
6

Manipulation de dates

Objectif
Sur les sites Internet, les dates sont omniprésentes, du simple
affichage de la date de publication d’un article à la saisie
précise d’un horaire de réservation d’un moyen de transport.
Le JavaScript propose nativement de nombreuses
fonctionnalités de manipulation des dates ou des heures et
des fonctions de formatage de leur affichage.

_ 6.1 L’OBJET DATE


Un objet Date en JavaScript contient l’ensemble des informations
nécessaires à la définition d’une date et d’une heure précise.
D’autres informations sont déduites automatiquement de la date,
comme le jour de la semaine correspondant.
Voici les différentes données présentes dans un objet Date :
L’année (fullYear) est un nombre à 4 chiffres.
Le mois (month) est un nombre variant de 0 (janvier) à 11
(décembre).
Le jour (date) est un nombre de 1 à 31.
Le jour de la semaine (day) est un nombre variant de 0
(dimanche) à 6 (samedi).
Les heures (hours) varient de 0 à 23.
Les minutes (minutes) varient de 0 à 59.
Les secondes (seconds) varient aussi de 0 à 59.
Les millisecondes (milliseconds) varient de 0 à 999.
La zone contient le décalage horaire exprimé en minutes entre
la date et le méridien de Greenwich.

6.1.1 Le constructeur Date()

◆ Les paramètres du constructeur

Un objet Date est créé avec le constructeur Date(), qui supporte


plusieurs formes de syntaxe selon le nombre de paramètres.
Sans paramètre, le constructeur retourne la date et l’heure de l’appel
sur le poste de l’utilisateur :
var dtJour=new Date();

Le constructeur peut aussi attendre un seul paramètre, le nombre de


millisecondes écoulées depuis le 1er janvier 1970 :
new Date(millisecondeDepuis01011970);

Le constructeur peut recevoir trois paramètres :


new Date(annee, mois, jour)

Dans ce cas, l’heure de la date créée est fixée à 00:00:00.


Le constructeur peut aussi recevoir jusque six paramètres pour
définir en plus l’heure de l’objet créé :
new Date(annee, mois, jour, heure, minute, seconde)

L’objet Date est intelligent. Il est capable de transformer une date qui
n’existe pas en date réelle. Ainsi, la création d’une date fixée au
29 février 2019 crée en fait une date au 1er mars 2019. Voici
quelques exemples de création de dates :
var jour1=new Date(2019, 11, 25, 23, 59, 59);
var jour2=new Date(2019, 1, 29);
var jour3=new Date(2019, 1, 28, 28, 0, 0);
var jour4=new Date(1234567890000);
console.log(jour1);
console.log(jour2);
console.log(jour3);
console.log(jour4);

La console contient les quatre dates valides équivalentes :

Wed Dec 25 2019 23:59:59 GMT+0100 (CET)


Fri Mar 01 2019 00:00:00 GMT+0100 (CET)
Fri Mar 01 2019 04:00:00 GMT+0100 (CET)
Sat Feb 14 2009 00:31:30 GMT+0100 (CET)

Notez que les indices des mois sont systématiquement réduits d’une
unité. Le mois de février a comme indice 1.
L’affichage brut des objets Date utilise un format ISO. Il a l’avantage
d’être universellement reconnu par tous les langages. Mais il n’est
pas franchement intuitif pour un humain.

◆ Définir une date avant le 1er janvier 1970

La date du 1er janvier 1970 est importante pour la gestion des dates.
Elle est considérée comme l’an zéro de l’informatique. Toutes les
dates sont ainsi exprimées par le nombre de secondes les séparant
de l’an zéro. Heureusement, le JavaScript peut aussi manipuler des
dates plus anciennes.
Attention au format anglo-saxon des dates
Les dates en JavaScript sont pensées dans le format anglo-
saxon. La semaine commence le dimanche et les dates sont
exprimées avec le mois placé avant le jour. De plus, le
décalage d’une unité sur l’indice du mois est systématique.
Ces particularités peuvent être déroutantes et génératrices de
bug sur les déclarations de vos dates.

6.1.2 Les méthodes de lecture

L’objet Date ne possède pas de propriétés directement accessibles.


Pour accéder aux informations de l’objet, le passage par des
méthodes de lecture est indispensable. Toutes les méthodes de
consultation commencent par get pour indiquer leur rôle de
récupération d’information. Elles n’attendent pas de paramètre, car
elles ne font que retourner une donnée.
Voici la liste des méthodes d’accès aux propriétés par ordre
alphabétique :
getDate() retourne la date de 1 à 31.
getDay() retourne l’indice du jour de la semaine de 0 à 6.
getFullYear() retourne l’année sur quatre chiffres.
getHours() retourne les heures.
getMilliseconds() retourne les millisecondes.
getMinutes() retourne les minutes.
getMonth() retourne l’indice du mois de 0 à 11.
getSeconds() retourne les secondes.
getTime() retourne le temps écoulé depuis le 1er janvier 1970.
getTimezoneOffset() retourne le décalage horaire en minutes.
Toutes ces méthodes existent aussi avec le préfixe getUTC à la
place du préfixe get et retournent les mêmes informations dans le
système de temps UTC.
Ces méthodes de lecture permettent de créer une fonction
d’affichage des dates dans un format plus lisible :
function formatDate(dt) {
// Définitions des tableaux de libellés des jours et des mois
var jours=new Array("Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi",
"Samedi");
var mois=new Array("Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet",
"Août", "Septembre", "Octobre", "Novembre", "Décembre");
var j=dt.getDay();
var d=dt.getDate();
if (d==1) {d+="er";} // La date est le 1, on affiche 1er
var m=dt.getMonth();
var y=dt.getFullYear();
var h=dt.getHours();
if (h<10) {h = "0" + h} // L'heure sur un caractère, on ajoute un 0
var i=dt.getMinutes();
if (i<10) {i = "0" + i} // Les minutes sur un caractère, on ajoute un 0
var s=dt.getSeconds();
if (s<10) {s = "0" + s} // Les secondes sur un caractère, on ajoute un 0
// Retour des différents éléments concaténés
return jours[j]+" "+d+" "+mois[m]+" "+y+" "+h+":"+i":"+s;
}

Téléchargez cette fonction à l’adresse http://tjs.ovh/date.


Notre fonction formatDate() retourne une chaîne représentant la date
au format français. Ici le résultat formaté pour les quatre dates
créées plus haut :

Mercredi 25 Décembre 2019 23:59:59


Vendredi 1er Mars 2019 00:00:00
Vendredi 1er Mars 2019 04:00:00
Samedi 14 Février 2009 00:31:30

Notez que le JavaScript propose l’objet DateTimeFormat, qui facilite


grandement le formatage de dates. Cet objet est détaillé ici en fin de
ce chapitre.
6.1.3 Les méthodes de modification

Les propriétés d’un objet Date sont modifiables par un ensemble de


méthodes, préfixées par set pour indiquer leur rôle d’affectation des
informations.
Les méthodes de modification des propriétés sont identiques aux
méthodes get. Elles attendent en paramètre la nouvelle valeur à
affecter à la propriété.
Les méthodes d’affectation ne retournent pas de valeur car elles
modifient directement la date sur laquelle elles s’appliquent. Ainsi,
pour forcer l’année de la date dt à l’an 2000, il suffit d’écrire :
dt.setFullYear(2000);

Notez que la méthode d’affectation du jour de la semaine n’existe


pas. Le jour de la semaine est calculé à partir des autres
informations de la date. La méthode d’affectation du fuseau
horaire n’est pas non plus disponible.

6.1.4 Les opérations sur les dates

Le JavaScript ne propose pas directement d’opérations de calcul sur


les dates. Il existe de petites astuces mathématiques pour créer
l’objet « date de la veille » ou calculer le nombre de jours séparant
deux dates.
La date d’hier se crée à partir de la date du jour à laquelle on enlève
un jour, exprimé en millisecondes, soit 24 heures fois 60 minutes fois
60 secondes fois 1 000 millisecondes :
var today=new Date();
var hier=new Date(today.getTime() - 24*60*60*1000);

On peut aussi se passer de la variable de travail today en ne créant


que hier et en lui affectant la nouvelle valeur de temps avec
setTime() :
var hier=new Date();
hier.setTime(hier.getTime() - 24*60*60*1000);

Pour calculer le nombre de jours séparant deux dates, le passage


par les valeurs de temps est de nouveau utilisé. Pour calculer le
nombre de jours avant Noël, il suffit de créer la date de Noël et de
comparer avec la date voulue :
var jour=new Date(2019, 2, 29);
var noel=new Date(jour.getFullYear(), 11, 25); // Décembre en indice 11
var nb=(noel.getTime()-jour.getTime())/(24*60*60*1000);
console.log(formatDate(jour));
console.log(formatDate(noel));
console.log(nb);

La console contient les deux dates à comparer, formatées avec


notre fonction créée plus haut, et le nombre de jours les séparant :

Vendredi 29 Mars 2019 00:00:00


Mercredi 25 Décembre 2019 00:00:00
271

_ 6.2 LES MINUTERIES


Il faut bien noter qu’une date n’est pas une horloge et ne se met pas
à l’heure en continu. Une date créée par le constructeur Date() est
fixée avec les paramètres de sa création ou avec ceux de la dernière
modification réalisée par les méthodes d’affectation.
Le JavaScript propose deux systèmes de minuterie qui déclenchent
une action, via le lancement d’une fonction, au bout d’un certain
délai exprimé en millisecondes.
La fonction setTimeout() déclenche une fonction une seule fois
après un délai exprimé en millisecondes. Elle retourne un objet
minuterie :
Object setTimeout(Function fonction, Integer delai)
La fonction setInterval() déclenche une fonction à chaque intervalle
de temps exprimé en millisecondes. Elle retourne aussi un objet
minuterie :
Object setInterval(Function fonction, Integer intervalle)

Ces deux fonctions retournent un objet minuterie qu’il est possible


de détruire avec les méthodes clearTimeout() et clearInterval(). La
minuterie passée en paramètre s’interrompt et aucune action ne
sera effectuée à l’issue du délai.
Un exemple d’usage de setInterval() est détaillé ici.

_ 6. 3 L’AFFICHAGE FORMATÉ DE DATES


Pour un site à vocation internationale, l’affichage des dates est un
exercice délicat. Il faut à la fois gérer les fuseaux horaires et les
différents formats d’affichage de dates, sans compter la traduction
des jours et des mois. Même pour un site francophone, la question
des fuseaux horaires peut se poser.

6.3.1 Le format international

Le JavaScript propose l’objet Intl, abréviation d’« international », et


son objet fils DateTimeFormat, pour formater des dates et des
heures. Son usage est très similaire à NumberFormat(), vu au
chapitre sur les mathématiques.
Le constructeur DateTimeFormat() attend deux paramètres
optionnels :
DateTimeFormat Intl.DateTimeFormat([String local, JSON option])

local est une chaîne décrivant le format de langue et de pays.


Par exemple "fr-FR". Si le paramètre est absent, le format local
du navigateur est utilisé.
option est un objet au format JSON contenant des propriétés
précisant le format local :
• hour12 vaut true ou false pour afficher ou non l’heure sur
24 heures ;
• weekday, year, month, day, hour, minute, second et
timeZoneName définissent le format d’affichage de chaque
élément de la date et de l’heure. Ils peuvent avoir comme
valeurs les chaînes "numeric", "long", "short", "2-digit".
À partir de cet objet paramétré, la méthode format() retourne la date
et l’heure au format défini. Pour n’afficher que l’heure, il faut définir
les options :
var intl=new Intl.DateTimeFormat("fr-FR", {hour12: false, hour: "2-digit", minute: "2-
digit", second:"2-digit"});
var dt=new Date();
console.log("Il est exactement "+intl.format(dt));

Les propriétés fixées à "2-digit" ajoutent automatiquement l’éventuel


0 devant les valeurs de l’heure :

Il est exactement 16:03:05

Pour obtenir un équivalent à notre fonction de formatage de date


créée plus haut, les options sont :
var int2=new Intl.DateTimeFormat("fr-FR", {hour12: false, weekday: "long",
year:"numeric", month:"long", weekday:"long", day:"numeric", hour: "2-digit", minute:
"2-digit", second:"2-digit"});
var dt=new Date();
console.log(int2.format(dt));

L’affichage de la date est :

mardi 27 mars 2018 à 14:02:00

Le même appel avec les locales "en-US", "es-ES" ou "de-DE"


donnerait :
Tuesday, March 27, 2018, 14:02:00
date.html:84 martes, 27 de marzo de 2018 14:02:00
date.html:87 Dienstag, 27. März 2018, 14:02:00

Le navigateur connaît les traductions et les formats de chaque pays.


Pas besoin d’utiliser des tableaux de libellés pour avoir la date dans
la bonne langue.

6.3.2 Remarques sur la chaîne locale

La chaîne locale est une chaîne structurée contenant :


Un code langue, sur deux caractères en minuscules.
Un code pays ou région précisant le format, sur deux
caractères en majuscules.
Le séparateur est le tiret. D’autres options très avancées pour les
langues complexes comme le chinois, le japonais ou l’arabe sont
disponibles pour tenir compte d’un calendrier spécifique.
La locale "fr" désigne la langue française. Les locales "fr-FR" et "fr-
CA" ajoutent que le pays est la France ou le Canada. Pour la
France, la date est affichée selon ce format :

mercredi 28 mars 2018 à 11:25:09

Pour le Canada, l’heure est présentée différemment :

mercredi 28 mars 2018 11 h 25 min 09 s

Les formats d’affichage changent donc également selon les pays,


pour une même langue.
La méthode supportedLocalesOf() retourne les formats de langues
et pays connus par le navigateur parmi ceux passés en paramètre :
Array Intl.DateTimeFormat.supportedLocalesOf(Array locales);
7

Les tableaux

Objectif
Nous avons déjà utilisé à plusieurs reprises les tableaux de
données dans des exemples des chapitres précédents. En
programmation, les tableaux sont des structures de données
incontournables et très souvent utilisées. Nous allons dans ce
chapitre comprendre pourquoi les tableaux sont si utiles,
apprendre à les créer et à les utiliser, explorer toutes les
possibilités de manipulations des tableaux en JavaScript, et
enfin créer quelques fonctions qui manquent au langage.

_ 7.1 UTILITÉ DES TABLEAUX


Un tableau (array en anglais) est une structure ordonnée de
données qui permet l’accès aux informations qu’il contient par un
indice.
Par exemple, le tableau mois des mois de l’année contient ces
données :
Indice Libellé du mois de l’année
1 Janvier
2 Février


12 Décembre
En programmation, l’indice est placé entre crochets. Le libellé du
mois à l’indice 1 est donc noté mois[1].
Ainsi, l’index en toute fin de ce livre présente les tableaux du
langage JavaScript avec les crochets après leurs noms pour faciliter
l’identification de leur type.
Un tableau peut contenir toutes les informations imaginables, par
exemple les jours de la semaine, les marques d’un catalogue
produits, les acteurs d’une liste de films, les auteurs des articles d’un
blog, les mots clés d’une discussion…
Un tableau facilite grandement la programmation et évite des
structures conditionnelles devenues inutiles. Supposons que la
variable m contienne le numéro de mois de l’année. Sans tableau,
pour trouver le libellé du mois, il faudrait un script du type :
switch (m) {
case 1:
nomMois="Janvier";
break;
case 2:
nomMois="Février";
break;

case 12:
nomMois="Décembre";
break;
}

En supposant le tableau mois déjà créé, il suffit de cette écriture


pour alimenter la variable de libellé du mois nomMois :
nomMois=mois[m];
_ 7.2 L’OBJET ARRAY
Maintenant que l’intérêt des tableaux en programmation est établi,
voyons comment les créer en JavaScript.

7.2.1 Création d’un tableau

Le JavaScript utilise le constructeur Array() pour retourner un nouvel


objet ayant la structure d’un tableau.
Sans paramètre, Array() retourne un tableau vide, sans aucun
élément ni aucun indice :
var vide=new Array();

Avec plusieurs paramètres, Array() retourne un tableau des


éléments passés dans les paramètres :
var jours=new Array("Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim");

Avec comme seul paramètre un nombre entier n, Array() crée un


tableau contenant n éléments, pas encore renseignés :
var cinq=new Array(5);

En affichant ces trois tableaux dans la console, on obtient bien nos


trois tableaux, soit un vide, un contenant les sept jours de la
semaine et un contenant cinq éléments non déclarés :

[]
(7) ["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"]
(5) [empty × 5]

La console supporte la fonction table(), qui affiche le tableau passé


en paramètre dans un format visuel facilitant l’exploration des
données :
console.table(jours)
Pour notre tableau jours, le résultat est :

On observe tout de suite que l’indice du premier élément vaut 0. En


JavaScript, comme dans la plupart des langages, les indices
commencent à 0.
Généralement, les données d’un tableau sont cohérentes entre
elles. Le tableau jours contient uniquement des libellés sous forme
de texte. Rien n’interdit techniquement de créer un tableau avec des
éléments de types disparates.
L’accès aux éléments du tableau se fait avec l’indice noté entre
crochets. Cet accès est autorisé en lecture et en écriture, c’est-à-dire
qu’il est possible de modifier le contenu d’élément du tableau :
jours[5]="SAMEDI";
jours[6]="DIMANCHE";

Après l’exécution de ces deux lignes, le tableau jours a les libellés


du week-end passés en majuscules. Cette écriture serait aussi la
solution pour remplir notre tableau vide.

7.2.2 La taille d’un tableau

La taille d’un tableau est donnée par la propriété length


(« longueur » en français). La taille est exprimée par un nombre
entier :
vide.length; // vaut 0
jours.length; // vaut 7
cinq.length; // vaut 5

Le JavaScript est tellement permissif et souple que la propriété length est


modifiable par programmation. La syntaxe vide.length=10 modifie la taille du tableau
vide et lui affecte un espace de 10 éléments !
Comme le premier indice commence toujours à 0, le dernier élément
est à la position length-1.
La propriété de longueur permet aussi d’ajouter un nouvel élément à
la fin d’un tableau. Le dernier élément actuel du tableau étant à la
position length-1, le prochain élément ajouté sera à la position
length. Il suffit d’affecter une valeur à cet indice pour ajouter un
élément au tableau, sans avoir à calculer manuellement ou anticiper
la taille du tableau :
vide[vide.length]=2;
vide[vide.length]=4;
vide[vide.length]=6;
vide[vide.length]=8;
console.log(vide);

La console affiche le tableau vide, qui n’est maintenant plus vide du


tout :

(4) [2, 4, 6, 8]

En PHP, il existe la syntaxe abrégée [], sans la propriété de


longueur, pour atteindre le dernier élément d’un tableau et lui
affecter une valeur. L’instruction vide[]=10; aurait ajouté un
cinquième élément au tableau en PHP, mais le JavaScript
retournera une erreur.

7.2.3 Les tableaux au format JSON

Nous verrons dans le chapitre dédié au format JSON (JavaScript


Object Notation) toute la puissance de cette notation.
Concentrons-nous uniquement sur la partie déclarative des tableaux
du format JSON.
La syntaxe new Array() peut être remplacée par la notation des
tableaux du JSON qui reprend les crochets :
mois=["Jan","Fév","Mar","Avr","Mai","Jui","Jul","Aou","Sep","Oct","Nov","Déc"]
Grâce aux crochets, la présence de la structure de tableau est
évidente. Cette notation évite des mots clés inutiles, rendant le code
à la fois plus lisible et plus compact. Ces deux objectifs sont à
l’origine de la création du format JSON.
Dans le même esprit, un tableau vide serait déclaré par :
var empty=[];

7.2.4 Convertir un tableau en chaînes de caractères

Un tableau possède la méthode toString(), qui le convertit en


chaînes de caractères faciles à lire sur une seule ligne. L’affichage
dans la console de jours.toString() retourne :

Lun,Mar,Mer,Jeu,Ven,Sam,Dim

Le tableau possède aussi la méthode join() (« regrouper » en


français), qui attend en paramètre une chaîne de caractères servant
de séparateur à chaque élément du tableau :
String tableau.join(String separateur)

La méthode toString() est donc équivalente à join(",").


Pour rendre la liste lisibile, nous pouvons utiliser un séparateur sur
deux caractères avec jours.join(", ") qui retourne :

Lun, Mar, Mer, Jeu, Ven, Sam, Dim

7.2.5 Vérifier le format d’un tableau

Le type d’un tableau retourné par l’opérateur typeof est object. Ce


retour est parfaitement logique car un tableau est bien un objet créé
par le constructeur Array().
Pour s’assurer qu’une variable contient bien un tableau, il faut utiliser
la fonction liée au constructeur Array.isArray(), qui retourne très
logiquement true si le paramètre est un tableau.
Voici les retours de tests de type sur quelques variables :
console.log(typeof jours); // retourne object
console.log(Array.isArray(jours)); // retourne true
console.log(Array.isArray(vide)); // retourne true
console.log(Array.isArray(8)); // retourne false

7.2.6 La copie de tableaux

Les tableaux utilisent, en JavaScript et dans la plupart des langages


de programmation, une structure de mémoire particulière qui interdit
leur duplication de manière classique, comme pour une variable
simple. Pour copier le contenu d’une variable dans une autre, il suffit
d’écrire :
var b=a;

À la suite de cette copie, les deux variables sont indépendantes et la


modification de l’une n’a aucun effet sur l’autre.
Ce n’est pas du tout le cas pour les tableaux :
var jours=["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"];
var days=jours;
days[0]="Mon";
console.log(jours);
console.log(days);

La console contient :

(7) ["Mon", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"]


(7) ["Mon", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"]

Les tableaux jours et days font référence au même tableau en


mémoire et une modification de l’un entraîne la modification de
l’autre.
Pour réaliser une copie dissociée d’un tableau, il est nécessaire de
parcourir les éléments du tableau source un à un et de les copier
dans le nouveau tableau :
var jours=["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"];
var days=[];
for (var i=0; i<jours.length; i++) {
days[i]=jours[i];
}
days[0]="Mon";
console.log(jours);
console.log(days);

Avec ce mode de copie, une action sur le tableau days n’a aucun
impact sur le tableau jours. La console affiche donc deux tableaux
différents :

(7) ["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"]


(7) ["Mon", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"]

_ 7.3 LES TABLEAUX SPÉCIAUX


7.3.1 Les tableaux associatifs

L’adressage des éléments d’un tableau n’est pas nécessairement un


indice ordonné commençant par 0. Il est possible de définir des
indices sous forme de nombres ou de chaînes de caractères.

◆ Création de tableaux associatifs

Imaginons le tableau ayant comme indice le code monnaie et


comme valeur le nom entier de la monnaie :
Code monnaie Libellé de la monnaie
EUR Euro
USD Dollar américain
BTC Bitcoin
Pour créer notre tableau monnaies en JavaScript, nous ne pouvons
plus utiliser le créateur Array() avec le contenu du tableau en
paramètre. Il faut alimenter notre tableau ligne par ligne :
var monnaies=[];
monnaies["EUR"]="Euro";
monnaies["USD"]="Dollar américain";
monnaies["BTC"]="Bitcoin";
console.table(monnaies);
console.log(monnaies.length);

Retrouvez la construction du tableau des monnaies sur


http://tjs.ovh/monnaie.
Le tableau affiché dans la console correspond bien à notre tableau
préparatoire :

La longueur retournée avec length vaut 0. Un tableau associatif ne


fonctionne pas comme un tableau classique avec des indices auto-
incrémentés.

◆ Parcours d’un tableau associatif

La longueur du tableau associatif étant vue comme nulle, le parcours


du tableau ne peut pas se réaliser avec la boucle classique.
Heureusement, il existe une solution pour accéder à l’ensemble des
éléments.
L’instruction for … in parcourt un par un tous les indices d’un tableau
en donnant accès à la donnée stockée :
for (var code in monnaies) {
console.log(code+" : "+monnaies[code]);
}

La console affiche simplement les trois lignes du tableau :

EUR : Euro
USD : Dollar américain
BTC : Bitcoin

◆ Vérifier l’existence d’un élément

Il peut être utile de vérifier la présence ou non d’un élément dans un


tableau avant de l’utiliser. On peut supposer que notre tableau des
jours de la semaine est complet et qu’il ne sera jamais utilisé avec
des indices hors de la plage habituelle. En revanche, notre tableau
des monnaies n’est pas complet et il est nécessaire de vérifier que
l’indice existe avant de l’utiliser. Pour tester l’existence d’un élément
en JavaScript, l’idéal est d’utiliser l’opérateur typeof pour comparer à
l’objet undefined :
if (typeof monnaies["YEN"] !== "undefined") {
// L'indice YEN existe : le traitement peut se faire
} else {
console.log("indice YEN manquant");
}

7.3.2 Les tableaux multidimensionnels

Nous avons vu qu’un élément de tableau pouvait être de tous les


types connus par JavaScript. Dans cet esprit, un élément d’un
tableau peut aussi être un autre tableau. Le tableau principal est
ainsi multidimensionnel.
Dans la vie courante, les tableaux sont le plus souvent à deux
dimensions. Voici un exemple de tableau à deux dimensions très
classique, présentant des clients et leurs caractéristiques :
Nb Chiffre d’affaires
Client Age
commandes total
Pierre 24 5 807,50
Paul 37 3 462,35
Jacques 43 2 78,45
Voyons comment le définir en JavaScript et comment l’utiliser :
var clients=[];
clients["Pierre"] = [24, 5, 807.50];
clients["Paul"] = [37, 3, 462.35];
clients["Jacques"] = [43, 2, 78.45];
console.table(clients);

La console affiche le tableau équivalent à notre détail de clients :

Le nombre de commandes validées par Jacques est stocké dans


l’élément :
clients["Jacques"][1]

7.3.3 Les tableaux d’objets

On remarque tout de même que notre tableau de clients n’est pas


tout à fait équivalent à notre tableau préparatoire. Les colonnes sont
indicées et non pas nommées. Il est possible de créer un tableau
d’objets :
var clients =[
{nom:"Pierre", age:24, nb:5, ca:807.50},
{nom:"Paul", age:37, nb:3, ca:462.35},
{nom:"Jacques", age:43, nb:2, ca:807.50}
];
Dans la console, le tableau est affiché :

Pour accéder au nombre de commandes de Jacques, il faut écrire :


clients[2].nb

Pour plus d’informations sur les objets, reportez-vous au chapitre


dédié à partir d’ici.

_ 7.4 LA MANIPULATION DE TABLEAUX


Dans ce paragraphe, nous allons lister toutes les fonctionnalités des
tableaux ayant une compatibilité avec l’ensemble des navigateurs
dans leur dernière version. Il existe d’autres méthodes qui ne sont
pas compatibles avec Internet Explorer, rendant leur usage
impossible sur un site public.
De nombreuses méthodes des tableaux attendent en paramètre une
fonction destinée à réaliser un traitement sur chacun des éléments
du tableau. Cette fonction est appelée fonction callback,
littéralement « fonction de rappel ». Cette fonction peut être une
fonction nommée classiquement ou une fonction anonyme (voir
définition).
ECMAScript 6 permet aussi l’écriture compacte utilisant la notation
de fonction fléchée =>. La syntaxe est expliquée ci-après dans le
paragraphe sur la fonction every().

7.4.1 Les méthodes de l’objet Array

◆ concat()
La méthode concat() retourne un nouveau tableau, constitué de la
fusion entre le tableau sur lequel elle s’applique et les tableaux
passés en paramètres :
Array tableau.concat(Array tableau1 [, … Array tableauN])

◆ every()

La méthode every() (« chaque » en français) teste si l’ensemble des


éléments du tableau sur lequel elle s’applique vérifie la condition
testée par la fonction passée en argument. Si un seul des éléments
du tableau ne remplit pas la condition, la méthode every() arrête le
parcours des éléments et retourne false.
La fonction de test attend trois paramètres :
La valeur en cours de traitement.
L’index du tableau.
Le tableau complet.
Boolean tableau.every(Function tester(element, [indice [, tableau]]))

Pour tester qu’un tableau ne contient que des nombres pairs, il suffit
d’appeler every() avec une fonction anonyme en paramètre :
var nombres=[4,8,1000,1500];
console.log(nombres.every(function(valeur) {
return (valeur%2==0);
})); // Retourne true

Rappel du chapitre sur les mathématiques : %2 retourne le reste


de la division par 2. Si le reste vaut 0, le nombre est pair.

Avec le tableau nombres contenant [4,8,1000,1500,3], le retour


aurait été false.
La fonction anonyme peut être remplacée par la fonction fléchée
=>, qui se présente sous la forme :
valeurEntree => expressionRetour
Ici, l’appel pourrait s’écrire simplement :
console.log(nombres.every(valeur => valeur%2==0 ));

Attention, la méthode every() ne fonctionne pas avec un tableau


associatif. Le parcours des éléments ne peut se faire que si le
tableau est indexé de manière classique.

◆ filter()

La méthode filter() retourne un nouveau tableau contenant tous les


éléments du tableau initial qui remplissent la condition définie dans
la fonction passée en paramètre. La fonction de filtre attend les trois
paramètres de valeur, d’indice et de tableau initial.
Array tableau.filter(Function filtrer(element, [indice [, tableau]]))

Pour filtrer tous les éléments pairs d’un tableau, il faut écrire :
var nombres=[4,3,8,11,12,15];
var pairs=nombres.filter(function(valeur) {
return (valeur%2==0);
});
console.log(pairs.toString()); // Affiche 4,8,12

Attention, la méthode filter() ne fonctionne pas avec un tableau


associatif.

◆ forEach()

Dans la même idée, les tableaux peuvent faire appel à la méthode


forEach() (littéralement « pour chaque »), qui parcourt un à un les
éléments du tableau et exécute une fonction sur chaque élément
trouvé :
tableau.forEach(Function toDo(element, [indice [, tableau]]))
Notez que forEach() ne retourne aucune valeur et ne modifie pas le
tableau initial car son but est simplement d’exécuter la fonction sur
les éléments du tableau. Il est possible de modifier le tableau en
utilisant les trois paramètres de la fonction appelée.
Pour forcer en majuscules un tableau de textes, on utilise donc :
jours.forEach(function(jour, indice, tableau) {
tableau[indice]=jour.toUpperCase();
});
console.log(jours.toString()); // Affiche LUN,MAR,MER,JEU,VEN,SAM,DIM

Attention, la méthode forEach() ne fonctionne pas avec un


tableau associatif.

◆ indexOf() et lastIndexOf()

La méthode indexOf() appliquée à un tableau renvoie l’indice du


premier élément trouvé après un indice de départ. Si aucun élément
ne correspond dans le tableau, la méthode retourne -1.
Integer tableau.indexOf(recherche [, Integer depart])

La méthode lastIndexOf() retourne l’indice du dernier élément trouvé


et -1 si l’élément n’est pas trouvé.
Integer tableau.lastIndexOf(recherche)

L’équivalence avec les méthodes de recherche sur les chaînes de


caractères n’est pas un hasard.

◆ join()

La méthode join() retourne une chaîne de caractères composée des


éléments du tableau rassemblés par un séparateur passé en
paramètre. Voir ici pour des exemples.
String tableau.join(String separateur)

◆ map()

La méthode map() (« carte » en français) retourne un nouveau


tableau composé des éléments du tableau initial auxquels une
fonction est appliquée.
Array tableau.map(Function mapping(element, [indice [, tableau]]))

Pour créer un nouveau tableau des mois en majuscules, la syntaxe


compacte ECMAScript 6 suffit :
var majMois=mois.map(m => m.toUpperCase());
console.log(majMois.toString());
// Affiche JAN,FÉV,MAR,AVR,MAI,JUI,JUL,AOU,SEP,OCT,NOV,DÉC

Attention, la méthode map() ne fonctionne pas avec un tableau


associatif.

◆ pop()

La méthode pop() supprime le dernier élément d’un tableau. Elle ne


retourne pas de résultat car elle modifie directement le tableau initial
sur lequel elle s’applique. On parle donc de méthode modificatrice.
tableau.pop()

Si le tableau initial n’a plus d’élément, tableau devient undefined.

◆ push()

La méthode push() ajoute les paramètres passés dans le tableau sur


lequel elle s’applique. La méthode push() est également
modificatrice et ne retourne pas de résultat.
tableau.push(element1 [, elementN])
◆ reduce()

La méthode reduce() réduit le tableau en appliquant une fonction


accumulatrice sur deux éléments pour n’en retourner qu’un seul.
La fonction accumulatrice est appelée jusqu'à ne retourner qu’un
seul élément. Elle attend quatre paramètres :
accumulateur : valeur retournée par le précédent appel à la
fonction.
element : l’élément en cours de traitement.
index : l’indice de l’élément.
origine : le tableau d’origine.
tableau.reduce(Fonction accumule(accumulateur, element, [indice [, origine]]))

Cette méthode est idéale pour calculer sans aucune boucle la


somme d’un tableau :
var valeurs=[10,20,30,40,50];
var somme=valeurs.reduce(function(accu, valeur) {return accu+valeur;});
console.log(somme); // Affiche 150 soit 10+20+30+40+50

Observons plus en détail par un affichage dans la console les


différentes étapes de la réduction du tableau :
var cumul=valeurs.reduce(function(accu, valeur, indice, origine) {
console.log("Indice = "+indice+" : accu="+accu+" / valeur="+valeur+" -
TAB="+origine.toString());
return accu+valeur;
});

La console affiche les quatre étapes de réduction :

Indice = 1 : accu=10 / valeur=20 - TAB=10,20,30,40,50


Indice = 2 : accu=30 / valeur=30 - TAB=10,20,30,40,50
Indice = 3 : accu=60 / valeur=40 - TAB=10,20,30,40,50
Indice = 4 : accu=100 / valeur=50 - TAB=10,20,30,40,50
Pour calculer la moyenne du tableau, il suffit de calculer la somme et
de diviser par le nombre d’éléments au dernier passage de
l’accumulateur :
var moyenne=valeurs.reduce(function(accu, valeur, indice, origine) {
if (indice<origine.length-1) {
return accu+valeur;
} else {
return (accu+valeur)/origine.length;
}
});
console.log(moyenne); // Affiche 30 soit 150 / 5 éléments

◆ reduceRight()

La méthode reduceRight() est identique mais réduit le tableau par la


droite au lieu de la gauche.

◆ reverse()

La méthode modificatrice reverse() inverse l’ordre des éléments du


tableau initial. Elle n’attend aucun paramètre et ne retourne aucun
résultat.
tableau.reverse()

◆ shift()

La méthode modificatrice shift() est proche de pop(). Elle n’attend


pas de paramètre et supprime le premier élément du tableau initial.
tableau.shift()

◆ slice()
La méthode slice() (« trancher » en français) retourne un nouveau
tableau composé de l’extrait du tableau initial entre l’indice de début
inclus et l’indice de fin exclu.
Array tableau.slice(Integer indiceDebut, Integer indiceFin)

◆ some()

La méthode some() est semblable à every() à la différence qu’il suffit


qu’un seul élément corresponde à la fonction de test passée en
paramètre.
Boolean tableau.some(Function tester(element, [indice [, tableauOrigine]]))

◆ sort()

La méthode modificatrice sort() trie le tableau par ordre croissant ou


par ordre alphabétique. Elle ne retourne aucun résultat et modifie
directement le tableau sur lequel elle s’applique.
tableau.sort()

En appliquant ensuite la méthode reverse() au tableau, le tri dans


l’ordre inverse est réalisé.
Il est possible d’ajouter une fonction callback en paramètre de sort()
pour réaliser des traitements de tri plus complets sur des tableaux
contenant des données enrichies.
tableau.sort(Function compareAetB(elementA, elementB))

Reprenons notre tableau d’objet clients pour le trier par ordre


alphabétique des noms de clients :
clients.sort(function(a, b) {
if (a.nom>b.nom) { return 1; }
if (a.nom<b.nom) { return -1; }
return 0;
});

Après exécution, le tableau clients n’a plus le même ordre :


Attention, la méthode sort() ne fonctionne pas avec un tableau
associatif.

◆ splice()

La méthode modificatrice splice() écrase une tranche du tableau


défini par l’indice de début inclus et un nombre d’éléments. Les
éléments facultatifs 1 à N sont alors insérés à la place de la tranche
supprimée. Le tableau initial est directement modifié.
tableau.splice(Integer indiceDebut, Integer nbElement [, elt1, …, eltN)

Les méthodes slice() et splice(), qu’on pourrait imaginer sœurs,


montrent un exemple perturbant d’incohérence du langage
JavaScript. La méthode slice() attend un indice de fin de tranche,
tandis que splice() attend un nombre d’éléments de tranche.

◆ unshift()

La méthode modificatrice unshift() insère des éléments au début du


tableau sur lequel elle s’applique.
tableau.unshift(element1 [, …, elementN])

À la lecture de ces méthodes de traitement sur les tableaux, il


apparaît que de nombreuses fonctionnalités modifient directement le
tableau initial. La maîtrise de la copie de tableaux est à connaître.

7.4.2 Les fonctionnalités manquantes


Par rapport à d’autres langages comme le PHP, le JavaScript
présente encore quelques manques dans la manipulation des
tableaux. Quelques exemples de nouvelles fonctionnalités sont
présentés dans ce paragraphe.

◆ Mélanger un tableau

Écrivons l’équivalent de la fonction shuffle() (« mélanger » en


français) de PHP, qui mélange un tableau de façon aléatoire. Pour
cela, nous allons simplement le trier en utilisant Math.random() en
paramètres du tri :
function shuffle(tab) {
tab.sort(function(a,b) {
return Math.random()-Math.random();
});
}
shuffle(mois);
console.log(mois.toString());

Retrouvez la fonction suffle() sur http://tjs.ovh/shuffle.


Chaque exécution modifie le tableau mois et affiche dans la console
par exemple :

Nov,Jan,Mar,Jui,Aou,Fév,Sep,Déc,Oct,Mai,Avr,Jul

La méthode random() est détaillée dans le chapitre sur l’objet Math.

◆ Extraire des éléments au hasard

La fonction array_rand() de PHP retourne une ou plusieurs valeurs


du tableau au hasard.
Écrivons la fonction arrayRand() en JavaScript selon ce schéma :
Mixed arrayRand (Array tableau [, Integer nb = 1 ] )
Le type noté Mixed signifie que le retour peut être ici soit un élément
seul soit un tableau d’éléments.
Pour extraire des éléments d’un tableau au hasard, il suffit de
recycler notre fonction shuffle() et d’extraire les nb premiers
éléments. Comme shuffle() modifie le tableau en entrée, nous allons
utiliser une copie temporaire du tableau :
function arrayRand(tableau, nb=1) {
/* Création du tableau temporaire de travail */
var tmp=[];
tableau.forEach(function(e) {
tmp.push(e);
});
/* Mélange */
shuffle(tmp);
/* Retour du 1er élément ou d'une tranche */
if (nb==1) {
return tmp[0];
} else {
return tmp.slice(0, nb);
}
}
console.log(arrayRand(mois));
console.log(arrayRand(mois,3).toString());

Retrouvez la fonction arrayRand() sur http://tjs.ovh/arand.


La console contient sur la première ligne un seul élément et sur la
seconde un tableau de trois éléments :

Jan
Nov,Jan,Jui

◆ Dédoublonner un tableau

La fonction array_unique() de PHP retourne un tableau composé


des éléments uniques du tableau d’entrée afin d’en supprimer les
doublons.
Écrivons notre fonction arrayUnique() en JavaScript :
Array arrayUnique(Array tableau)

Nous allons créer un tableau temporaire et vérifier avec indexOf() si


la nouvelle valeur est déjà présente :
function arrayUnique(tableau) {
var tmp=[];
tableau.forEach(function(e) {
if (tmp.indexOf(e)<0) { // e n'est pas dans tmp
tmp.push(e);
}
});
return tmp;
}
var doublons=["Jean", "Gérard", "Pierre", "Paul", "Jean", "Paul", "Jacques"];
console.log(doublons.toString());
console.log(arrayUnique(doublons).toString());

Retrouvez ce script sur http://tjs.ovh/unique.


La console contient bien le tableau d’origine et le tableau des
prénoms sans doublon :

Jean,Gérard,Pierre,Paul,Jean,Paul,Jacques
Jean,Gérard,Pierre,Paul,Jacques

Pour aller plus loin


Les fonctionnalités manquantes sont finalement simples à
développer en utilisant les méthodes natives du JavaScript.
Nous verrons également dans le chapitre suivant comment créer
de nouvelles méthodes directement sur le constructeur au lieu de
fonctions indépendantes.
8

Programmation objet et JSON

Objectif
Le JavaScript est un langage objet et nous avons déjà utilisé
dans les chapitres précédents quelques notions de la POO,
programmation orientée objet (OOP en anglais pour Object-
Oriented Programmation). Il est maintenant temps de rentrer
dans le détail de ce concept et des particularités du
JavaScript.

_ 8.1 LES PRINCIPES


DE LA PROGRAMMATION OBJET
Tous les langages modernes offrent aux développeurs la possibilité
d’utiliser la programmation objet. Ce type de programmation consiste
à manipuler des objets qui contiennent des données structurées
(stockées dans des propriétés) et des fonctions (appelées
méthodes). La programmation objet remplace le développement plus
traditionnel, qui s’appuie sur des appels de fonctions avec des
paramètres d’entrée.
La programmation objet facilite la maintenance du code et sa
modularité. En principe, un ensemble de fonctionnalités est porté par
un objet et chaque objet a son rôle dans un projet. Cette séparation
logique et fonctionnelle favorise le travail à plusieurs.
Un objet est associé à un type, appelé classe de l’objet, définissant
la structure des propriétés et ses fonctionnalités. Un objet, appelé
instance, contient les données liées à la structure.
Dans cette première partie de chapitre, nous allons d’abord étudier
les concepts importants de la programmation objet. Imaginons
l’instance monArticle, issue du type d’objet Article, et contenant les
propriétés suivantes :
Propriété Valeur pour monArticle
"Mon nouveau livre sur le
titre
JavaScript"
auteur "Olivier"
illustration "photo-couverture-livre.jpg"
date 01/12/2018
introduction "…"
texte "…"
Les propriétés de l’objet monArticle, instance du type Article,
seraient accessibles par la notation pointée :
monArticle.titre

Les objets de type Article posséderaient également des méthodes,


par exemple l’affichage de la vignette de résumé :
monArticle.displayResume()

En programmation classique, non objet, l’affichage de la vignette se


ferait avec un appel à la fonction :
displayResume(auteur, titre, date, illustration)
Traditionnellement et pour suivre la nomenclature des objets
natifs du JavaScript, les classes d’objets commencent toujours
par une majuscule. Ainsi, article est une instance de classe
Article, tout comme l’objet natif window est un objet de type
Window.

8.1.1 Les propriétés

Une propriété est une donnée associée à un objet. La propriété


possède un nom et sa valeur, qui peut être une chaîne de
caractères, un nombre, un booléen ou tout autre objet JavaScript.
Une propriété est dite accessible en écriture s’il est possible de
modifier sa valeur par programmation.
Si la propriété n’est pas modifiable, on parle d’un accès en lecture
seule.
Une propriété est dite énumérable si son nom est présent dans la
liste des propriétés.
Les propriétés et leur type d’accès sont définis dans le constructeur
de l’objet.

8.1.2 Les méthodes

Une méthode est une fonction associée à un objet. Par exemple, la


méthode log(valeur) de window est la fonction qui affiche le
paramètre valeur dans la console du navigateur. Par abus de
langage et simplification, méthode et fonction sont souvent
synonymes.
Dans le corps de la méthode d’un objet, le mot clé this fait référence
à l’objet en cours de manipulation, c’est-à-dire celui sur lequel
s’applique la méthode.

8.1.3 La notation pointée


Nous avons déjà utilisé partout la notation pointée dans les scripts
d’exemple de ce livre. Ainsi, le titre de la page contenu dans la
balise <title> est accessible avec la notation document.title.
Le JavaScript autorise aussi l’accès aux propriétés d’un objet par les
crochets, comme s’il s’agissait d’un tableau. Ainsi, la syntaxe
document["title"] est fonctionnellement équivalente à document.title.
Il est intéressant de noter que les méthodes des objets peuvent
aussi être appelées par la notation crochet. Vous pouvez donc
afficher dans la console du navigateur le titre de la page avec
l’appel :
console["log"](document.title);

La lisibilité de cette syntaxe n’est pas vraiment bonne. De plus, votre


éditeur de texte ne pourra pas procéder à la coloration syntaxique
habituelle. Pourtant, l’accès aux propriétés par les crochets peut être
très pratique. Imaginons un objet qui possède les propriétés
montant1 à montantN et que nous devions en faire la somme. Avec
la notation pointée, nous n’avons pas le choix et nous devons coder
la totalité de la somme :
var somme=obj.montant1+obj.montant2+ … +obj.montantN;

Avec les crochets, nous pouvons utiliser une boucle sur un indice
variant de 1 à N et construire le nom de la propriété montantI. La
somme devient donc :
var somme=0;
for (var i=1 ; i<=N ; i++) {
somme+=obj["montant"+i];
}

Vous noterez que la notation pointée du JavaScript est


équivalente à la flèche en PHP. La syntaxe JavaScript
monArticle.titre devient $monArticle=>titre en PHP.
Notez qu’en PHP le point sert à concaténer deux chaînes de
caractères. La syntaxe JavaScript "Hello "+"World" s’écrit "Hello
"."World" en PHP.
8.1.4 Les classes

En programmation objet classique, une classe est la définition de la


structure d’un objet. Elle définit les propriétés disponibles, leur type
et leur accessibilité. La classe définit également les méthodes
disponibles sur l’objet.
Les classes sont un élément central de la programmation objet dans
tous les langages. Les classes ont été ajoutées dans le JavaScript à
partir de la version ECMAScript 2015.

8.1.5 Le constructeur

En JavaScript, la création d’un objet se fait à partir d’un constructeur.


Un constructeur est simplement une fonction définissant un objet,
ses propriétés et ses méthodes. Dans le corps de la fonction
constructeur, le mot clé this fait référence à l’instance de l’objet en
cours de construction.

8.1.6 L’héritage

L’héritage est une notion capitale de la programmation objet. Le


nom d’héritage est bien choisi, car il désigne la transmission
automatique de propriétés et de méthodes d’un objet père vers un
objet fils. Ce principe évite la duplication de code.
Par exemple, si nous devons définir des objets de types Chien et
Cheval, nous aurons besoin d’une classe parente Animal, qui
portera toutes les propriétés communes, comme l’âge, le sexe ou le
nom.
En JavaScript, tous les objets héritent du type de plus bas niveau,
Object, qui définit entre autres la méthode toString() qui transforme
en chaîne de caractères lisibles toutes les propriétés de l’objet.
De la même manière, les éléments HTML affichés sur la page sont
des objets issus d’une longue liste d’héritages permettant de
regrouper les propriétés communes.
_ 8.2 CRÉER DES OBJETS JAVASCRIPT
Après avoir défini les principales caractéristiques de la
programmation objet, nous allons apprendre à créer des objets et à
les utiliser en JavaScript. Malheureusement, le JavaScript utilise un
modèle particulier de création d’objets, à base de prototypes. La
longue histoire du langage a également abouti à des syntaxes très
variées. Rassurez-vous, la souplesse de JavaScript facilite l’usage
rapide des objets, même si tous les concepts ne sont pas
parfaitement maîtrisés à la première lecture.

8.2.1 Les constructeurs JavaScript

Un objet JavaScript est construit grâce à une fonction constructeur.


Cette fonction organise la structure de l’objet en définissant les
propriétés et les méthodes. Le constructeur initialise certaines
propriétés avec des valeurs de départ.
Le JavaScript possède des constructeurs natifs (comme Date, Array,
Object). Le développeur peut bien sûr créer ses propres objets.
Créons notre constructeur d’objet de type Animal :
function creerAnimal(nom, sexe, age, photo) {
this.nom=nom;
this.sexe=sexe;
this.photo=photo;
this.age=age;
this.male="Mâle";
this.femelle="Femelle";
this.log=function() {
var s = this.sexe=="F" ? this.femelle : this.male;
console.log(s+" "+this.nom+" - "+this.sexe+" - "+this.age+" ans - "+this.photo);
}
this.display=function() {
var s = this.sexe=="F" ? this.femelle : this.male;
return "<div class='animal'>"+s+" "+this.nom+"<img src='"+this.photo+"'></div>";
}
}

Le constructeur définit des propriétés de l’objet et les initialise avec


les paramètres d’entrée. Le constructeur crée également deux
méthodes d’affichage de l’objet.
Notez le mot clé this, qui fait référence à l’objet en cours de création.
L’appel du constructeur avec l’opérateur new crée une nouvelle
instance d’objet de type Animal :
var monElephant=new creerAnimal("Maximus", "M", 32, "elephant.jpg");
monElephant.log();

La console du navigateur affiche :

Mâle Maximus - M - 32 ans - elephant.jpg

8.2.2 L’usage des classes en JavaScript

La version ECMAScript 2015 a introduit les classes JavaScript, pour


se rapprocher de la syntaxe objet des autres langages de
programmation.
Notre constructeur creerAnimal peut devenir la classe Animal définie
par le script :
class Animal {
constructor(nom, sexe, age, photo) {
this.nom=nom;
this.sexe=sexe;
this.photo=photo;
this.age=age;
this.male="Mâle";
this.femelle="Femelle";
this.log=function() {
var s = this.sexe=="F" ? this.femelle : this.male;
console.log(s+" "+this.nom+" - "+this.sexe+" - "+this.age+" ans - "+this.photo);
}
this.display=function() {
var s = this.sexe=="F" ? this.femelle : this.male;
return "<div class='animal'>"+s+" "+this.nom+"<img src='"+this.photo+"'>
</div>";
}
}
}

La syntaxe de création de classe est très proche de la syntaxe du


constructeur. La fonction constructor initialise des propriétés et des
méthodes et utilise également le mot clé this.
L’initialisation d’un objet est d’ailleurs identique :
var monDauphin=new Animal("Skipper", "M", 22, "dauphin.jpg");

8.2.3 L’héritage en JavaScript

Notre objet de type Animal peut servir de support aux constructeurs


d’autres espèces d’animaux.

◆ L’héritage avec constructeurs

Avec la méthode du constructeur, nous allons définir creerChat(), qui


hérite des propriétés du constructeur creerAnimal() :
function creerChat(nom, sexe, age, photo) {
/* Héritage de creerAnimal */
this.creerAnimal=creerAnimal;
this.creerAnimal(nom, sexe, age, photo);
/* Particularités du chat */
this.cri="Miaulement";
this.male="Chat",
this.femelle="Chatte";
}

L’héritage se fait par les deux premières lignes du constructeur. La


suite de la fonction définit les caractéristiques d’un chat avec son cri
et le nom précis du mâle et de la femelle.
La création de l’objet garfield utilise toujours l’opérateur new :
var garfield=new creerChat("Garfield", "M", 8, "garfield.jpg");
garfield.log();

L’objet garfield possède les propriétés et méthodes issues du


constructeur creerAnimal() et les caractéristiques spécifiques à
creerChat(). La console affiche les propriétés :

Chat Garfield - M - 8 ans - garfield.jpg

◆ L’héritage par classes

Le mot clé extends permet d’indiquer qu’une classe fille hérite d’une
classe mère. La fonction super() appelle constructor() de la classe
mère :
class Chat extends Animal {
constructor(nom, sexe, age, photo) {
super(nom, sexe, age, photo);
this.cri="Miaulement";
this.male="Chat",
this.femelle="Chatte";
}
}

La création d’une instance de Chat est classique :


var kitty=new Chat("Hello Kitty", "F", 7, "kitty.jpg");
console.log("Cri de "+kitty.nom+" : "+kitty.cri);

La console affiche :

Cri de Hello Kitty : Miaulement

8.2.4 Programmation orientée prototype

Le JavaScript utilise un modèle objet assez unique dans le monde


des langages de programmation. Commençons par identifier
quelques particularités des objets que nous venons de créer :
console.log(typeof monElephant); // Affiche object
console.log(typeof monDauphin); // Affiche object

Les objets créés par classe ou par constructeur sont de type Object
pour le JavaScript, car ils dérivent du constructeur Object() de plus
bas niveau dans la hiérarchie objet du langage.
Pour obtenir le constructeur précis d’un objet, il faut utiliser sa
propriété constructor, qui retourne une référence vers sa fonction
constructeur. La propriété constructor.name donne le nom de la
fonction constructeur ou de la classe :
console.log(garfield.constructor.name); // Affiche creerChat
console.log(kitty.constructor.name); // Affiche Chat

◆ Le prototype d’un objet

Le JavaScript est un langage objet à base de prototypes, c’est-à-dire


de fonctions de création d’objets. Le prototype fournit de façon
dynamique des propriétés et des méthodes aux objets qui héritent
du prototype. Ainsi, une modification du prototype entraîne
immédiatement une modification de tous les objets déjà créés par ce
prototype.
La méthode Object.getPrototypeOf() retourne la fonction prototype
de l’objet passé en paramètre. Par exemple, pour nos chats
JavaScript :
console.log(Object.getPrototypeOf(garfield));
console.log(Object.getPrototypeOf(kitty));

La console affiche les prototypes qui ont servi à la construction des


objets garfield et kitty :
Le prototype retourné par getPrototypeOf() possède aussi un
constructeur dans la fonction constructor et un nom accessible avec
constructor.name.

◆ L’héritage par prototype

En utilisant une boucle parcourant un à un le prototype de création


d’un objet, il est facile de retrouver la chaîne d’héritage qui compose
l’objet. Écrivons la fonction getHeritages(), qui retourne le tableau de
tous les prototypes composant l’objet passé en paramètre :
function getHeritages(obj) {
/* Recherche du prototype initial */
var proto=Object.getPrototypeOf(obj);
/* Initialisation du tableau des héritages qui sera retourné */
var heritages=[];
/* boucle tant qu'un prototype est trouvé */
while (proto !== null) {
/* Ajout du nom de prototype dans le tableau */
heritages[heritages.length]=proto.constructor.name;
/* Recherche du prototype suivant dans la chaîne d'héritages */
proto=Object.getPrototypeOf(proto);
}
return heritages;
}

Voici quelques appels à getHeritages() sur des objets intéressants


de notre page :
console.log(getHeritages(garfield));
console.log(getHeritages(kitty));
console.log(getHeritages(kitty.age));
console.log(getHeritages(document))
var div=document.getElementById("monDiv");
console.log(getHeritages(div));

La console affiche les tableaux suivants :


["creerChat", "Object"]
["Chat", "Animal", "Object"]
["Number", "Object"]
["HTMLDocument", "Document", "Node", "EventTarget",
"Object"]
["HTMLDivElement","HTMLElement","Element","Node","EventTa
rget","Object"]

Le chat garfield a été construit sans utiliser l’héritage par Animal.


Le chat kitty possède le niveau Animal supplémentaire.
L’âge du chat est un nombre qui hérite de Object.
Les objets natifs du langage comme window ou un élément div de la
page ont une chaîne d’héritage particulièrement fournie.

8.2.5 Les propriétés d’un objet

◆ L’accès aux propriétés

Il existe plusieurs solutions pour lister toutes les propriétés d’un


objet. Dans tous les cas, seules les propriétés de type enumerable
sont listées.
La méthode keys(), appelée directement sur Object, retourne un
tableau contenant les noms des propriétés de l’objet passé en
paramètre :
Array Object.keys(Object monObjet)

Ainsi, l’appel de keys() sur notre chat kitty :


console.log(Object.keys(kitty));

affiche dans la console :


(9) ["nom", "sexe", "photo", "age", "male", "femelle", "log",
"display", "cri"]

La boucle for … in parcourt également les propriétés enumerable


d’un objet. Pour notre objet kitty, nous pouvons écrire la boucle :
for (var key in kitty) {
if (typeof kitty[key] === "function") { // key est une méthode
console.log(key+"() est une méthode");
} else {
console.log(key+" = "+kitty[key]);
}
}

L’affichage dans la console sera :

nom = Hello Kitty


sexe = F
photo = kitty.jpg
age = 7
male = Chat
femelle = Chatte
log() est une méthode
display() est une méthode
cri = Miaulement

L’ordre des propriétés est celui de leur création.

◆ Supprimer une propriété

La destruction complète d’une propriété se réalise avec l’opérateur


JavaScript delete, selon cette syntaxe générique :
delete monObjet.maPropriete
Définissons un objet représentant un PC :
var monPC={"cpu": "I7", "ram": "16 Go", "HDD": "2000"};
delete monPC.HDD;
console.log(monPC);

La console affiche l’objet PC sans la propriété HDD :

{cpu: "I7", ram: "16 Go"}

Notez que l’opérateur delete supprime bien la propriété de l’objet.


Une première idée pour supprimer la propriété serait de lui affecter
la valeur null avec monPC.HDD=null. Mais le résultat serait différent,
avec la propriété HDD toujours présente :

{cpu: "I7", ram: "16 Go", HDD: null}

◆ Les propriétés énumérables

Une propriété enumerable est listable dans les boucles for … in et


dans le retour de keys(). Certaines propriétés ne sont pas
énumérables. C’est le cas par exemple de la longueur length d’un
tableau, qui n’apparaît pas ici :
var tab=["Hello", "World"];
console.log(Object.keys(tab)); // (2) ["0", "1"]

La méthode getOwnPropertyNames() retourne toutes les propriétés


d’un objet. Ainsi le même appel sur tab :
console.log(Object.getOwnPropertyNames(tab));

affiche dans la console un tableau contenant les indices 0 et 1 et la


propriété length :

(3) ["0", "1", "length"]


◆ Définir les types de propriété

La méthode defineProperty() de Object permet de modifier, sur


l’objet monObjet passé en paramètre, la propriété propriete avec les
caractéristiques définies dans desc :
Object.defineProperty(Object monObjet, String propriete, JSON descr)

Le troisième paramètre est un objet au format JSON contenant les


nouvelles caractéristiques de la propriété :
configurable vaut true si la propriété est modifiable avec la
méthode defineProperty().
enumerable vaut true si la propriété apparaît dans la boucle for
… in.
value contient la valeur de la propriété.
writable vaut true si la valeur de la propriété est accessible en
écriture.
get est facultatif et contient la fonction d’accession à la valeur
de la propriété.
set est aussi facultatif et contient la fonction d’affectation de la
valeur de la propriété.
Créons une nouvelle instance potte de Chat et ajoutons-lui une
propriété :
var potte=new Chat("Potté", "M", 5, "potte.jpg");
Object.defineProperty(potte, "dons", {
value:["escrimeur", "danseur"],
writable: false,
enumerable: true
});

La propriété dons est un tableau de domaines d’activité où Chat


Potté est doué. Comme la propriété n’est pas writable, une nouvelle
affectation n’a aucun effet :
console.log(potte.dons); // Affiche (2) ["escrimeur", "danseur"]
potte.dons=["Séducteur"];
console.log(potte.dons); // Affiche toujours (2) ["escrimeur", "danseur"]
Notez que certaines méthodes comme defineProperty()
s’appellent directement sur Object et non pas sur l’instance de
l’objet comme attendu.

◆ La méthode create() de Object

L’objet racine Object possède la méthode create(), qui attend en


paramètre une fonction et retourne l’objet créé par cette fonction :
Object Object.create(Function constructeur [, JSON descripteur])

Le paramètre optionnel descripteur est un objet JSON qui définit les


propriétés de l’objet retourné, à l’image de defineProperty(). En
reprenant notre exemple de Chat, nous pouvons écrire :
var grominet=Object.create(Object.prototype, {
"nom":{"value":"Grominet"},
"sexe": {"value":"M"},
"age": {"value":"14"},
});

L’objet grominet vaut :

{nom: "Grominet", sexe: "M", age: "14"}

Il faut reconnaître qu’il ne s’agit pas de la manière la plus simple de


créer un objet en JavaScript.

◆ Le principe de polyfill

Grâce au modèle objet par prototypes du JavaScript, il est possible


de modifier en profondeur tous les objets déjà créés, même les
objets natifs du navigateur. Cette particularité est très intéressante
car elle permet de gérer simplement les navigateurs les plus anciens
qui ne possèdent pas certaines fonctionnalités. Cette technique
s’appelle polyfill.
Par exemple, pour éviter toute erreur de script sur les navigateurs
anciens qui ne supportent pas l’objet console, on peut l’ajouter à
l’objet natif window.
window.console=window.console || {
log:function() {},
table:function() {},
time:function() {},
timeEnd:function() {},
clear:function() {},
};

Ce script détecte si console existe. Sinon un objet au format JSON


(voir pages suivantes de ce chapitre) représentant la console et ses
méthodes est affecté.

◆ Étendre la console avec polyfill

La console des navigateurs accepte une mise en forme CSS


restreinte, mais qui peut se révéler utile.
Nous allons ajouter aux objets la méthode toConsoleStrings(), qui
retourne la valeur formatée par CSS du contenu de l’objet. L’idée est
de formater l’affichage en fonction du type d’objet :
Array moObject.toConsoleStrings()

Pour cela, nous allons utiliser les caractères de substitution de la


chaîne de caractères de la méthode log().
Voici les caractères de substitution supportés :
Caractère Usage
Remplacé par un entier (formaté
sur Firefox et Safari)
%i
%.2d affiche un nombre entier sur 2
digits
%f Remplacé par un nombre flottant
(formaté sur Firefox et Safari)
%.2f affiche un nombre avec 2
chiffres après la virgule
Remplacé par une chaîne de
%s
caractères
Met en forme CSS le reste de la
%c
chaîne
Voici quelques exemples de rendus :
var dt=new Date();
console.log("Il est %.2d:%.2d", dt.getHours(), dt.getMinutes());
console.log("Hello %s", navigator.appCodeName);
console.log("Ce produit vaut %.2f euros", 15.2);
console.log("kitty.nom=%c%s","font-weight:bold;font-style:italic",kitty.nom);

La console affiche :

Rendu de la console avec quelques exemples de caractères de substitution

Créons notre nouvelle méthode toConsoleStrings() sur l’ensemble


des constructeurs Number, String et Object :
String.prototype.toConsoleStrings = function(prefix="") {
return [prefix+"%c%s", "font-weight:bold;font-style:italic" ,this.toString()];
}
Number.prototype.toConsoleStrings = function(prefix="") {
return [prefix+"%c%s", "color:#00f;" ,this.toString()];
}

Object.prototype.toConsoleStrings = function(prefix="") {
return [prefix+this.toString()];
}

La méthode toConsoleStrings() est créée sur les constructeurs


String pour afficher le texte en gras italique, sur Number pour
l’afficher en bleu et sur l’ensemble des autres Object sans formatage
spécial. Un paramètre facultatif prefix permet de démarrer l’affichage
par un texte brut.
Créons également une nouvelle méthode display() sur la console :
window.console.display = function(obj, prefix="") {
console.log.apply(console, obj.toConsoleStrings(prefix));
}

La méthode log() est appelée sur la console avec les paramètres


retournés par notre fonction de formatage, grâce à apply() que nous
allons explorer immédiatement.
Pour afficher dans la console des valeurs formatées, il suffit
d’utiliser :
console.display(kitty.nom, "Nom de Kitty = ");
console.display(garfield.age, "Age de Garfield = ");

À l’exécution, la console affiche :

8.2.6 Les appels de fonctions avec apply() et call()

Nous venons de voir au paragraphe précédent un usage de la


méthode apply() (ici « mettre en application », en français) sur
l’appel à une fonction.
Les deux appels suivants produisent un résultat exactement
identique :
console.log("nom=%c%s", "color:#00f", kitty.nom);
console.log.apply(console, ["nom=%c%s", "color:#00f", kitty.nom]);

L’appel classique à une méthode s’écrit :


monObjet.maMethode(param1, …, paramN)

Avec apply(), l’écriture devient :


monObjet.maMethode.apply(monObjet, [param1, …, paramN])
L’appel avec apply() attend deux paramètres :
Le premier est souvent appelé thisArg, qui correspond à l’objet
this sur lequel s’appliquera la méthode. Si apply() est appelé
sur une fonction pure, ce paramètre peut être affecté à null.
Le second est un tableau contenant les paramètres d’appel
param1 à paramN de la fonction.
Le principal intérêt de apply() est de pouvoir passer un nombre non
fixe de paramètres lors d’un appel de fonction. C’est le cas dans
notre exemple de méthode console.display() du paragraphe
précédent, qui n’a pas toujours le même nombre de paramètres
selon les types d’objet à afficher dans la console.
Notez qu’il existe une autre méthode semblable d’appel indirect à
une méthode nommée call(). Le fonctionnement est identique à
apply() mais les paramètres sont passés dans l’ordre, sans utiliser
de tableau, selon la syntaxe générique suivante :
monObjet.maMethode.apply(monObjet, param1, …, paramN)

Il est possible d’utiliser call() pour gérer l’héritage des constructeurs :


function Personnel(nom) {
this.nom=nom;
}
function Informaticien(nom) {
Personnel.call(this, nom);
this.departement="DSI";
}
function Developpeur(nom, languages) {
Informaticien.call(this, nom);
this.languages=languages;
}
var moi=new Developpeur("Olivier", ["JS", "PHP"]);
console.log(moi);

La console affiche bien :


Developpeur {nom: "Olivier", departement: "DSI", languages:
Array(2)}
departement : "DSI"
languages : (2) ["JS", "PHP"]
nom : "Olivier"

_ 8.3 LE FORMAT JSON


Le JSON (JavaScript Object Notation) est un format texte
standardisé de représentation des données. Il est à la fois compact
et facile à lire et à écrire pour un développeur.
Le JSON a été inventé dans le début des années 2000 par le
développeur américain Douglas Crockford. Le JSON est maintenant
nativement reconnu par tous les navigateurs. Il est également
compris par tous les autres langages de programmation et devient
ainsi une sérieuse alternative au format d’échange de données XML
(Extensible Markup Language).
Le format JSON permet de définir des structures de données à partir
d’objets et de tableaux.
Un objet JSON est un ensemble de couples propriété:valeur placé
entre accolades. Les couples sont séparés par une virgule. La
propriété et sa valeur sont séparées par le caractère deux-points. La
structure générique d’un objet JSON est donc :
{ propriete1 : valeur1, …, proprieteN : valeurN }

Un tableau JSON est une suite de valeurs séparées par des virgules
et placée entre crochets. La structure générique d’un tableau JSON
est donc :
[ valeur1, …, valeurN ]

Une propriété d’un objet JSON est une chaîne de caractères.


Une valeur en JSON peut être une chaîne de caractères, un
nombre, un booléen ou un autre élément JSON, objet ou tableau.
Grâce à cette diversité de valeurs possibles, le format JSON peut
décrire pratiquement toutes les structures d’objets complexes
nécessaires dans un projet.
Notez quelques caractéristiques propres au format JSON :
L’ordre de déclaration des propriétés d’un objet n’a pas
d’incidence.
Il n’est pas possible de créer un tableau indexé avec JSON.
Seuls les tableaux avec indice auto-incrémenté sont autorisés.
Les espaces et retours à la ligne n’ont pas d’effet sur les objets
générés et servent uniquement à la mise en forme et à la
lisibilité.
Le seul délimiteur de chaînes de caractères est le guillemet ".
L’apostrophe ' n’est pas autorisée et génère une erreur.

8.3.1 L’usage du JSON en JavaScript

◆ La syntaxe générale

Le format JSON est tellement pratique et logique qu’il remplace


souvent les constructeurs de la déclaration native des objets et des
tableaux.
Ainsi, un tableau sera souvent déclaré par du JSON, à la fois plus
lisible et plus compact :
var jours=["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"];

La déclaration classique serait :


var jours =new Array("Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim");

Les deux créations ont exactement le même résultat. Le tableau


jours contient :
(7) ["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"]

Les tableaux sont détaillés dans le chapitre dédié à partir d’ici.


Pour les objets, la déclaration avec JSON facilite aussi la lisibilité et
la compacité. La création d’un objet sans propriété avec JSON
s’écrit simplement :
var obj={};

Cette syntaxe est équivalente à l’utilisation du constructeur Object()


classique :
var obj=new Object();

◆ Un exemple avec un poisson en JavaScript

Nous allons créer l’objet truiteFario par JSON, qui définit quelques
caractéristiques d’une truite fario :
var truiteFario={
nom: "Fario",
noms: ["Truite sauvage","Truite commune"],
latin: "salmo truta fario",
image: "fario.jpg",
particularite: "Présence de points rouges et noirs auréolés",
tailleMoyenne: 30,
tailleMaxi: 70,
maille: 25,
};

Dans la console, le navigateur affiche l’objet :

{nom: "Fario", noms: Array(2), latin: "salmo truta fario", image:


"fario.jpg", particularite: "Présence de points rouges et noirs
auréolés", …}
Ce simple exemple présente quelques caractéristiques
intéressantes :
Les propriétés de notre objet ne sont pas des chaînes avec les
délimiteurs ", comme le réclame la syntaxe JSON. Le
JavaScript autorise cette notation directe, plus compacte et
plus lisible.
La virgule sépare chaque couple de propriété/valeur. Il y a une
virgule ignorée avant l’accolade de fermeture. La syntaxe
JSON ne l’autoriserait pas.
La propriété noms est un tableau de chaînes de caractères.
La notation pointée est toujours utilisée pour accéder aux valeurs
des propriétés de notre objet. Par exemple, pour obtenir le nombre
de noms alternatifs de notre truite fario, nous écrivons :
truiteFario.noms.length

Comme pour les objets créés par leur constructeur natif, l’accès aux
propriétés peut se faire aussi par les crochets. Ainsi, le second nom
alternatif est trouvé indifféremment par :
truiteFario["noms"][1]

ou par :
truiteFario.noms[1]

◆ Créer des méthodes en JSON

Le JavaScript ajoute au format JSON le support de création de


méthode. Créons, pour notre truite fario, la méthode display(), qui
retourne le code HTML représentant la fiche poisson :
var truiteFario={
nom: "Fario",

display:function() {
var html="<h1>"+this.nom;
html+=" <span class=\"latin\">("+this.latin+")</span></h1>";
html+="<span class=\"noms\">"+this.noms.join(", ")+"</span>";
html+="<div class=\"image\"><img src=\"media/"+this.image+"\"></div>";
html+="<div class=\"particularite\">"+this.particularite+"</div>";
html+="<div>Taille moyenne : "+this.tailleMoyenne+" cm</div>";
html+="<div>Taille maxi : "+this.tailleMaxi+" cm</div>";
return html;
}
}

Pour afficher la fiche dans l’élément HTML poisson :


document.getElementById("poisson").innerHTML=truiteFario.display();

le résultat sur le navigateur est :

◆ Passage de paramètres en JSON

Le JSON est très utilisé pour passer des paramètres à une fonction.
L’avantage principal est de ne pas avoir à respecter l’ordre des
paramètres de la déclaration de la fonction. L’autre avantage est que
la définition de nouveaux paramètres est facilitée. Imaginons une
fonction qui attende dix paramètres nommés param1 à param10. La
déclaration classique serait :
function maFonction(param1, …, param10) { /* Traitements */ }

En utilisant le format JSON, la déclaration serait :


function maFonction(params) { /* Traitements */ }
Et l’appel utiliserait un objet JSON en paramètre :
maFonction( {param1:valeur1, …, param10:valeur10} ) ;

◆ Échange de données

Le format JSON est également très utilisé dans le cadre d’échange


de données entre le navigateur et le serveur, lors d’appels AJAX par
exemple, que nous traiterons ici, dans la seconde partie dédiée à
l’interactivité.

8.3.2 Les conversions JSON en JavaScript

Le constructeur JSON possède deux méthodes de manipulations


des objets.
La méthode parse() (« analyser » en français) transforme la chaîne
de caractères au format JSON passée en paramètre et retourne
l’objet correspondant :
Object JSON.parse(String chaineJSON)

Si la chaîne passée en paramètre n’est pas correctement formatée,


une erreur est retournée. Il est donc nécessaire de tester la
présence d’une erreur éventuelle avant toute analyse de chaîne
JSON avec le bloc try catch, que nous détailleronsici.
La méthode stringify() (« chaînifier » en traduction littérale) effectue
l’opération inverse, appelée linéarisation, et retourne la chaîne de
caractères JSON correspondant à l’objet passé en paramètre :
String JSON.stringify(Object monObjet)

Par exemple, pour reprendre notre objet truiteFario, l’appel à


JSON.stringify(truiteFario) retourne la chaîne de caractères :
{"nom":"Fario","noms":["Truite sauvage","Truite
commune"],"latin":"salmo truta
fario","image":"fario.jpg","particularite":"Présence de points
rouges et noirs auréolés","tailleMoyenne":30,"tailleMaxi":
70,"maille":25}

Notez que les propriétés sont bien des chaînes entourées des " et
qu’il n’y a pas de virgule après la dernière propriété. La méthode
display() a été ignorée car elle n’est pas compatible avec le format
JSON officiel.
La méthode stringify() retourne toujours la chaîne la plus courte
possible en supprimant tous les espaces inutiles.
Le JSON ne supporte pas directement les tableaux associatifs. La
linéarisation d’un tableau indicé de monnaies retourne un tableau
vide :
var monnaies=[];
monnaies["EUR"]="Euro";
monnaies["USD"]="Dollar américain";
monnaies["BTC"]="Bitcoin";
console.log(JSON.stringify(monnaies)); // Affiche un tableau vide []

Grâce à la double notation d’accès aux propriétés d’un objet, nous


pouvons transformer notre tableau associatif en objet classique :
var devises={}; /* Ici devises est un objet */
devises["EUR"]="Euro";
devises["USD"]="Dollar américain";
devises["BTC"]="Bitcoin";

Les notations devises["EUR"] et devises.EUR sont équivalentes. La


linéarisation de l’objet devises est maintenant possible :

{"EUR":"Euro","USD":"Dollar américain","BTC":"Bitcoin"}
_ 8. 4 RÉSUMÉ DE LA POO JAVASCRIPT
Terminons ce chapitre par un résumé des particularités de la
programmation orientée objet en JavaScript.

8.4.1 Toutes les syntaxes de création d’objet

La création d’un objet JavaScript peut se réaliser en plusieurs


syntaxes dont le résultat et le fonctionnement seront strictement
identiques. Prenons l’exemple de création des différentes espèces
de truites de nos rivières.

◆ La solution brute

Cette solution de création peut être qualifiée de brute car elle


n’utilise aucun des principes utiles de la programmation objet. Elle
est adaptée pour la création d’un objet unique. Créons l’objet
truiteArcEnCiel avec cette méthode :
var truiteArcEnCiel=new Object();
truiteArcEnCiel.nom="Arc en ciel";
truiteArcEnCiel.latin="salmo gairdneri";
truiteArcEnCiel.image="arcenciel.jpg";
truiteArcEnCiel.noms=new Array("Truite d'élevage", "Truite d'Amérique");
truiteArcEnCiel.particularite="Poisson originaire des États-Unis et introduit en
France";
truiteArcEnCiel.tailleMoyenne=30;
truiteArcEnCiel.tailleMaxi=80;
truiteArcEnCiel.display=displayTruite;

La fonction displayTruite() est affectée à la méthode display() de


notre truite arc-en-ciel.

◆ La solution avec constructeur


L’utilisation d’un constructeur facilite la création de plusieurs objets
de même type et évite des lourdeurs d’écriture. Le constructeur est
une fonction qui renseigne les propriétés de l’objet créé :
function Truite(nom, latin, image, noms, partic, tMoyenne, tMaxi) {
this.nom=nom;
this.latin=latin;
this.image=image;
this.noms=noms;
this.particularite=partic;
this.tailleMoyenne=tMoyenne;
this.tailleMaxi=tMaxi;
this.display=displayTruite;
}

L’appel au constructeur est précédé de l’opérateur new :


var saumonFontaine=new Truite("Saumon de fontaine", "salvinilus fontanilis",
"omble.jpg", ["Omble"], "Présence de points orange et noirs auréolés", 25, 50);

◆ Le format JSON

Nous avons déjà vu la création de la truite fario avec le format


JSON. C’est un format compact et lisible, mais il ne permet pas de
profiter de l’héritage. La différence ici est que nous réutilisons la
fonction displayTruite() pour éviter de la coder de nouveau dans le
JSON :
var truiteFario={
nom: "Fario",
noms: ["Truite sauvage","Truite commune"],
latin: "salmo truta fario",
image: "fario.jpg",
particularite: "Présence de points rouges et noirs auréolés",
tailleMoyenne: 30,
tailleMaxi: 70,
display:displayTruite,
}
◆ La création de classe

L’usage des classes, introduit par ES6, n’est qu’une syntaxe


alternative qui a pour but de se rapprocher de la programmation
objet classique. Cette syntaxe ne modifie pas le fonctionnement par
prototypes des objets JavaScript.
class Poisson {
constructor(nom, latin, image, noms, partic, tMoyenne, tMaxi) {
this.nom=nom;
this.latin=latin;
this.image=image;
this.noms=noms;
this.particularite=partic;
this.tailleMoyenne=tMoyenne;
this.tailleMaxi=tMaxi;
this.display=displayTruite;
}
}

Naturellement, rien n’empêche de mixer ces solutions dans le même


projet.
Retrouvez l’ensemble des déclarations disponibles sur le script de la
page http://tjs.ovh/truite.

8.4.2 Les différences avec la POO classique

Le JavaScript est un langage à part, à de nombreux points de vue.


Sa mise en œuvre de la programmation objet peut déconcerter mais
apporte malgré tout une souplesse très utile pour contourner les
limitations de certains navigateurs par exemple.
Parmi les différences, notons :
La très grande diversité des syntaxes de créations d’objets.
L’utilisation des prototypes qui permet de modifier en temps réel
tous les objets déjà instanciés.
Le principe de polyfill, qui permet de modifier et d’enrichir les
objets natifs du langage et du navigateur.
9

Réintroduction au JavaScript

Objectif
Ce chapitre présente toutes les nouveautés du JavaScript
depuis sa révision ECMAScript 6 de 2015 et quelques usages
du JavaScript largement utilisés. Sa lecture sera
particulièrement instructive pour les développeurs ayant une
connaissance plus ancienne du langage. Notez que
l’ensemble du livre utilise le JavaScript dans sa version ES6.
Ce chapitre est une synthèse rapide des évolutions, avec des
renvois vers d’autres chapitres.

_ 9.1 LES ÉVOLUTIONS DE STRUCTURE


DU LANGAGE
ECMAScript 6 a apporté de nombreuses améliorations dans la
compacité d’écriture du code. Cette compacité a parfois un impact
sur la lisibilité pour les développeurs qui ne connaîtraient pas les
nouvelles syntaxes.

9.1.1 Raccourcis logiques

Les opérateurs de conditions && et || sont le plus souvent utilisés


avec des valeurs booléennes pour réaliser des tests conditionnels.
En réalité, les opérateurs renvoient l’une des valeurs comparées. Ce
mode de fonctionnement permet de réaliser des raccourcis dans des
expressions d’affectation de valeurs.
Cette syntaxe est très souvent utilisée pour détecter d’anciens
traitements qui auraient pu affecter une valeur :
var monTableau = monTableau || [];

Ici, la variable monTableau n’est pas modifiée si elle existe déjà.


Sinon, un tableau vide est affecté.
Avec l’opérateur &&, l’usage est moins fréquent :
var name = myObject && myObject.getName();

Cette syntaxe retourne le résultat de la méthode getName() de


myObject.

9.1.2 Les fonctions anonymes

Une fonction anonyme est une fonction qui a été créée sans être
nommée. Le plus souvent, les fonctions anonymes sont créées
lorsqu’elles ne sont appelées qu’une seule fois, à l’endroit de leur
création. Par exemple, dans la méthode map() d’un tableau :
var textes=["Bonjour","tout","le","monde"];
console.log(textes.map(function(v) {return v.length;})); // (4) [7, 4, 2, 5]

Ce script retourne un tableau avec le nombre de caractères de


chacun des éléments.
Voir d’autres exemples de fonctions anonymes ici.
Une fonction anonyme peut aussi être affectée à une variable ou
une constante.
const updateTitle=function(t) {
document.title=t;
};

L’appel se fait simplement avec des parenthèses pour recueillir les


paramètres, après le nom de la fonction, qui n’est donc plus
vraiment anonyme :
updateTitle("Nouveau titre de la page");

9.1.3 Les fonctions auto-exécutées

Une fonction auto-exécutée est une fonction anonyme qui est


immédiatement appelée par la notation IIFE (Immediately Invoked
Function Expression). Cette notation est très souvent utilisée par les
appels de services externes. Nous verrons un exemple d’appel du
script analytics.js de Google ici de ce même chapitre.
La création et l’appel de la fonction se présentent sous cette forme :
(function(p1, …, pN) {
/* Traitements de la fonction */
})(v1, …, vN);

La création de la fonction anonyme avec ses paramètres p1 à pN est


placée entre parenthèses pour signifier qu’elle doit s’exécuter
immédiatement, et les valeurs v1 à vN sont passées en paramètres
d’exécution de la fonction.

9.1.4 Les fonctions fléchées

La notation des fonctions fléchées (arrow functions) est l’exemple


typique de syntaxe visant à optimiser la compacité du code. Cette
notation astucieuse, utilisant les caractères =>, est même finalement
assez lisible.
Si la fonction attend un seul paramètre, la syntaxe générique est :
p => { /* Instructions */ };

Si la fonction attend plusieurs paramètres, les parenthèses sont


obligatoires :
(p1, …, pN) => { /* Instructions */ };

Nous pouvons réécrire notre fonction de mise à jour du titre avec :


const updateTitle2 = t=>{ document.title=t; };
Si la fonction n’a pas de paramètre, on utilise les parenthèses vides :
() => { /* Instructions */ };

Si l’instruction de la fonction retourne une expression, on écrit :


(p1, …, pN) => { return expression };

La syntaxe peut être encore réduite à :


(p1, …, pN) => expression;

9.1.5 Les fonctions internes

Une fonction peut contenir dans son code des déclarations d’autres
fonctions intermédiaires nécessaires au traitement principal. Ces
fonctions internes (inner functions) ne sont pas visibles, ni
utilisables, dans le reste du code en dehors de la fonction mère.
La syntaxe générique est :
function principale(v1, v2, v3) {
function interne(p1, p2) {
/* return expression */
}
/* Instructions */
}

La fonction interne() n’est utilisable que dans la fonction principale().


Elle est considérée comme une fonction de travail temporaire,
comme le seraient des variables de traitements internes.

9.1.6 Le reste des paramètres d’une fonction

Une fonction peut être déclarée avec un nombre indéfini de


paramètres en entrée. La syntaxe utilise trois points … placés
devant le dernier paramètre :
function maFonction(v1, v2, …restes) { /* Instructions */ }
Dans cette fonction, restes est un tableau Array contenant les
paramètres suivants v1 et v2. Il est possible de définir une fonction
avec uniquement le reste des paramètres :
function avg(…restes) {
var sum=0;
for (var i=0; i<restes.length; i++) {
sum+=restes[i];
}
return sum/restes.length;
}

L’appel se fait simplement avec avg(10, 20, 30, 40, 50).

9.1.7 Chaînes de caractères multi-lignes

Le JavaScript ne permet pas, avec les délimiteurs de chaînes


classiques " et ', de créer des chaînes de texte sur plusieurs lignes.
Le nouveau séparateur ` a été ajouté pour faciliter l’écriture de code
avec de longs textes. Voir ici la déclaration de chaînes de
caractères.

9.1.8 Déclaration de variables et de constantes

ECMAScript 6 a ajouté deux types de déclarations de variables en


plus du classique var.
La déclaration avec let crée une variable dont la portée est limitée au
bloc d’instructions dans lequel elle est déclarée. Voir ici les portées
des variables.
La déclaration avec const crée une constante qui n’est pas
modifiable après son initialisation. Voir ici l’utilisation des constantes.

_ 9.2 LES ÉVOLUTIONS SUR LES OBJETS


9.2.1 class et extends

ECMAScript 2015 a introduit la syntaxe de création d’objet par


classe avec les mots clés class, extends et super(). Cette syntaxe
n’apporte aucun changement dans le fonctionnement objet de
JavaScript. La syntaxe par classe a pour seul objectif de se
rapprocher des autres langages de programmation, avec une
structure du type :
class fille extends mere {
constructor(p1, …, pN) {
super(p1, …, pX);
this.propI=pI;
}
}

Voir ici du chapitre sur la programmation objet pour la définition des


classes et de l’héritage objet.

_ 9.3 COMPRENDRE UN APPEL


JAVASCRIPT
9.3.1 Le cas d’un appel au script analytics.js
de Google Analytics

Google fournit de nombreux services pour les éditeurs de site web.


Le plus connu est probablement Google Analytics, qui propose des
statistiques d’audience gratuites et puissantes. Pour en profiter, il
suffit de se créer un compte et de copier-coller ces quelques lignes
sur toutes les pages du site à analyser :
<!-- Google Analytics --> <script> (function(i,s,o,g,r,a,m)
{i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||
[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)
[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })
(window,document,'script','https://www.ganalytics.com/analytics.js','ga'); ga('create',
'UA-XXXXX-Y', 'auto'); ga('send', 'pageview'); </script> <!-- End Google Analytics -->

Nous allons essayer d’analyser et de comprendre ce que ce script


va réaliser sur notre page.
La première étape est de décompacter et d’indenter les quelques
lignes de cet appel, qui a optimisé le moindre octet en supprimant
les espaces et les points-virgules non nécessaires.
Le script devient, après une remise en forme :
On repère donc rapidement la notation de fonction anonyme auto-
exécutée, qui permet ici de nommer les paramètres d’entrée avec
des variables sur un seul caractère pour gagner encore de l’espace.
(function(i,s,o,g,r,a,m){
i['GoogleAnalyticsObject']=r;
i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)}, i[r].l=1*new Date();
a=s.createElement(o),
m=s.getElementsByTagName(o)[0];
a.async=1;
a.src=g;
m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.ganalytics.com/analytics.js','ga');
ga('create', 'UA-XXXXX-Y', 'auto'); /* A paramétrer avec votre compte */
ga('send', 'pageview');

Le premier paramètre i de la fonction est alimenté avec window. La


ligne qui contient i['GoogleAnalyticsObject']=r; ajoute donc la
propriété GoogleAnalyticsObject sur l’objet window et l’initialise avec
le paramètre r, qui vaut 'ga' à l’exécution.
La ligne suivante est plus subtile et peut se réécrire ainsi :
i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)
}

Il faut comprendre i[r] en utilisant les paramètres d’entrée et donc


comme window["ga"]. Cette notation signifie que si i[r] n’existe pas
encore, il faut lui affecter la fonction anonyme :
(i[r].q=i[r].q||[]).push(arguments)

La fonction window.ga() est créée. Elle utilise encore l’opérateur ||


pour initialiser le tableau q avec un tableau vide s’il n’existe pas
encore. L’appel à push() ajoute au tableau q les paramètres passés
via arguments.
Ensuite, la partie i[r].l=1*new Date(); est une astuce d’écriture pour
retourner le nombre de secondes depuis le 1er janvier 1970, tel que
le ferait la méthode getTime().
Les quatre lignes suivantes servent à ajouter au document un appel
asynchrone du script analytics.js. En remplaçant les paramètres, le
code devient plus compréhensible :
a=document.createElement("script"),
m=document.getElementsByTagName("script")[0];
a.async=1; /* 1 qui signifie true */
a.src='https://www.ganalytics.com/analytics.js';
m.parentNode.insertBefore(a,m)

Nous étudierons en détail, à partir d’ici, la création et l’ajout


d’éléments HTML au document.
À la fin de l’appel de la fonction auto-exécutée, window["ga"] est une
fonction. Nous avons vu au chapitre précédent que les propriétés
d’un objet peuvent être accédées par les crochets ou par la notation
pointée. La propriété window.ga est donc la même fonction. Comme
window est l’objet maître, il peut être supprimé de l’appel. L’appel
direct à la fonction ga() est donc correct :
ga('create', 'UA-XXXXX-Y', 'auto'); /* A paramétrer avec votre compte */

Ici, le service Google Analytics est démarré. La ligne suivante envoie


un message au service indiquant que la page est vue.
À la fin du script, le tableau window.ga.q contient deux lignes avec
les arguments de chacun des appels :
(2) [Arguments(3), Arguments(2)]
Arguments(3) ["create", "UA-XXXXX-Y", "auto"]
Arguments(2) ["send", "pageview"]

Une fois le script analytics.js chargé, il s’exécute et traite les


données en attente dans le tableau window.ga.q.

Google Analytics possède de très nombreuses fonctionnalités


pour mesurer l’audience et également les événements sur votre
site, comme les actions de partages sur les réseaux sociaux,
l’ajout au panier, le paiement, etc. Ces données sont ensuite
visibles dans une interface puissante qui permet de comprendre
les chemins de conversion, de tester des variantes et d’ainsi
optimiser le site.

Même si ce script ne fait rien d’extraordinaire, il utilise de


nombreuses astuces du langage JavaScript :
Il est très compact.
Il crée une nouvelle fonction sur l’objet window.
Il ajoute un appel asynchrone à un script externe.
Il s’assure que des insertions multiples de ce bout de code ne
seront pas pénalisantes.
Il gère l’ensemble des traitements en mode asynchrone pour ne
pas pénaliser les temps de chargement du site analysé.

9.3.2 Le mode strict

Le mode strict du JavaScript rend le navigateur plus exigeant vis-à-


vis du code à exécuter. L’activation du mode strict est réalisée en
ajoutant "use strict" au début d’un fichier .js ou d’une balise script :
"use strict";
/* Traitements */
L’activation du mode strict peut se limiter à une fonction :
function maFonction() {
"use strict";
/* Traitements */
}

Le mode strict remplace de simples avertissements (warning) en


véritables erreurs bloquantes. Ainsi, l’utilisation d’une fonction non
déclarée remonte une erreur :
"use strict";
variableInconnueB=1;

L’erreur remontée est :

Uncaught ReferenceError: variableInconnueB is not defined

Consultez l’exemple à l’adresse http://tjs.ovh/strict pour voir la


différence de comportement.
Le mode strict optimise l’interprétation du code JavaScript dans le
navigateur et accélère les traitements.
10

L’objet maître window

Objectif
L’objet window étant l’objet de plus haut niveau dans la
hiérarchie des objets JavaScript, il est le support de
nombreux autres objets et de quelques fonctionnalités que
nous allons étudier dans ce chapitre.

_ 10.1 L’OBJET WINDOW


Un objet window unique existe pour chaque fenêtre contenant du
code HTML. Une fenêtre, et donc un objet window associé, sont
créés dans chacune de ces situations :
Un onglet du navigateur.
Le navigateur entier s’il n’a qu’un seul onglet ouvert.
Un pop-up.
Un cadre (frame en anglais) inclus dans un document.

La notation pointée d’un sous-objet ou d’une méthode window par


window. n’est pas obligatoire. Les notations window.open() et
open() ou window.navigator.userAgent et navigator.userAgent
sont donc fonctionnelles.
10.1.1 Les boîtes de dialogue

Nous avons déjà présenté, au chapitre 2, dans l’exemple « Hello


World 4 » , l’utilisation de window.alert() qui affiche une boîte de
dialogue présentant un message d’information à l’utilisateur. Nous
avons également vu que ce système d’affichage peut être ignoré par
l’utilisateur, ce qui rend cette fonctionnalité peu fiable.
Le JavaScript propose trois boîtes de dialogue de ce style. Il est utile
de les connaître mais préférable de ne pas les utiliser sur des sites
en production.

◆ Boîte d’information

La boîte d’information présente un message texte défini par le


développeur et un seul bouton [OK] ou [Fermer].
La position de la boîte, sa forme et le bouton ne sont pas
modifiables. Ils sont formatés par le navigateur pour être en
harmonie avec le système d’exploitation de l’utilisateur.
Le titre n’est pas modifiable non plus. Il est géré par le navigateur. Il
est parfois vide mais contient en général l’adresse de la page qui a
déclenché l’ouverture de la boîte. Seul le texte à l’intérieur peut être
personnalisé.
Dans une boîte de dialogue, le code HTML n’est pas interprété mais
affiché directement sous forme de texte.
Cet appel affichera simplement le texte brut :
alert("<center>Hello<br>World</center>");

Et voici quelques exemples de rendus différents, selon les


navigateurs :
Ici, il n’est pas possible de formater le texte avec du centrage, de la
mise en gras, etc. La seule option disponible est le retour à la ligne,
qui s’encode par \n.
L’appel suivant affichera bien une fenêtre de message sur plusieurs
lignes :
alert("Hello World\n\nAvec des sauts de ligne !");

◆ Boîte de confirmation

Le deuxième type de boîte de dialogue permet de demander une


confirmation à l’utilisateur, par l’intermédiaire de deux boutons [OK]
et [Annuler]. Très logiquement, cette confirmation s’ouvre avec
confirm() :
Boolean confirm(String question)

La méthode confirm() retourne un booléen qui vaut true si l’utilisateur


a cliqué sur [OK] ou tapé la touche [Entrée] et false s’il a fermé la
boîte avec la touche [Echap] ou le bouton [Annuler].
La confirmation s’utilise souvent directement dans une instruction
conditionnelle :
if (confirm("Voulez-vous valider votre commande ?")) {
/* Traitement de la commande */
} else {
/* Traitement d'annulation */
}

À l’affichage, le visiteur devra répondre à cette question :

◆ Boîte de saisie

Enfin, un troisième type de boîte permet à l’utilisateur de saisir une


information sous forme de texte. L’appel se fait avec la méthode
prompt() (« demander » / « solliciter », en français), qui retourne une
chaîne de caractères :
String prompt(String message)

Cette ligne affecte la variable qte avec la saisie de l’utilisateur :


var qte=prompt("Indiquez la quantité souhaitée :");

Si l’utilisateur ferme la boîte avec [Annuler] ou [Echap], le retour sera


null. Pour tester la validité de la saisie, on pourrait écrire :
if (qte) {
/* Traitement saisie faite */
} else {
console.log("Pas de saisie");
}
Attention, le retour de prompt() est bien une chaîne de caractères.
Pensez à la convertir en nombre si vous attendez une quantité
par exemple.

◆ Rappel sur les limites

En théorie, ces trois boîtes de dialogue sont dites modales, c’est-à-


dire qu’elles sont bloquantes et interdisent à l’utilisateur toute action
dans le document de la fenêtre. Le navigateur Safari grise le reste
de la page pour montrer qu’aucune action n’est possible tant que la
boîte n’est pas fermée. Si le navigateur détecte un trop fort usage de
ces boîtes bloquantes, il peut proposer à l’utilisateur de les
désactiver, rendant ainsi toutes les interactions prévues impossibles.
Nous verrons, à partir d’ici, dans le chapitre sur les formulaires de la
seconde partie dédiée à l’interactivité, comment obtenir des saisies
fiables de la part des utilisateurs.

10.1.2 Les informations du navigateur

L’objet window porte le sous-objet navigator, qui possède de


nombreuses propriétés destinées à identifier le navigateur de
l’utilisateur et ses possibilités. Voici un tableau des propriétés
communes à l’ensemble des navigateurs modernes.
Propriété Utilisation
Chaîne complète définissant
userAgent
le navigateur
Nom du navigateur (pas
appName
fiable)
Code du navigateur (pas
appCodeName
fiable)
Version du navigateur (pas
appVersion
fiable)
language Langue du navigateur
Système d’exploitation de
platform
l’utilisateur
Indicateur booléen sur
onLine
l’accès Internet
Indicateur booléen sur
cookieEnabled
l’acceptation des cookies
Nombre de cœurs logiques
hardwareConcurrency (threads) du processeur de
l’appareil
La propriété userAgent est la seule vraiment utile. Les propriétés
appName et appCodeName ne sont pas fiables et retournent par
exemple « Mozilla » et « Netscape » pour Internet Explorer !

Les propriétés de navigator sont définies par défaut dans le


navigateur. Il existe des plug-ins destinés à simuler le
fonctionnement d’autres navigateurs ou d’autres dispositifs
(comme des smartphones ou des tablettes). Il ne faut surtout pas
considérer que ces propriétés sont fiables et correspondent à
l’appareil (ou device en anglais) du visiteur.

L’objet navigator possède aussi un objet geolocation, destiné à la


géolocalisation précise, que nous étudierons dans la seconde partie
dédiée à l’interactivité, à partir d’ici.

Chaque navigateur sur chaque plateforme possède sa propre


chaîne userAgent. Il est très compliqué d’identifier de manière
fiable un appareil, en particulier des smartphones. Des
bibliothèques partagées par des développeurs sont disponibles
en ligne gratuitement en faisant une recherche sur « mobile
detect ».
10.1.3 Les informations de l’écran

L’objet screen de window présente des propriétés sur l’écran de


l’appareil de l’utilisateur.
Propriété Signification
Largeur totale de l’écran
width
en pixels
Hauteur totale de l’écran
height
en pixels
Largeur de l’écran
availWidth
disponible en pixels
Hauteur de l’écran
availHeight
disponible en pixels
Profondeur de couleur en
colorDepth
nombre de bits
L’objet screen possède de plus un sous-objet orientation, destiné
essentiellement aux dispositifs mobiles, qui donne des informations
sur la position d’utilisation de l’appareil.
La propriété screen.orientation.angle donne l’angle en degrés de
l’écran.
La propriété screen.orientation.type contient un libellé texte
correspondant à l’angle :
portrait-primary : tenue verticale en mode portrait.
portrait-secondary : tenue verticale tête en bas.
landscape-primary : tenue horizontale en mode paysage (avec
le bas de l’appareil sur la gauche).
landscape-secondary : tenue horizontale en mode paysage
(avec le bas de l’appareil sur la droite).
Attention, certains navigateurs ne possèdent pas l’objet
orientation. Il est nécessaire de tester son existence avant de
l’utiliser en JavaScript.

10.1.4 La barre de statut

Les navigateurs actuels n’ont plus de barre de statut, ligne


permettant d’afficher des informations dans le bas de la fenêtre.
Cette disparition provient sans aucun doute des navigateurs sur
smartphones, qui ont besoin d’optimiser l’espace disponible à
l’écran.
La propriété status existe toujours mais n’a plus aucun effet.

10.1.5 L’objet location

Le sous-objet location de window contient des propriétés définissant


l’URL en cours de consultation :
Propriété Signification
href URL complète de la page
Domaine de la page et son
host
numéro de port
Domaine de la page (sans le
hostname
port)
port Port d’écoute (vide par défaut)
pathname Chemin relatif de la page
protocol Protocole http: ou https:
Paramètres de la recherche,
search c’est-à-dire après le caractère ?
inclus
Propriété Signification
Partie de l’URL qui suit le
hash
caractère # inclus
Page d’origine de la page en
origin
cours de consultation
Par exemple, pour l’URL :
http://www.toutjavascript.com/reference/chercher.php?mot=location#res

Les propriétés sont :

hash:"#res"
host:"www.toutjavascript.com"
hostname:"www.toutjavascript.com"
href:"http://www.toutjavascript.com/reference/chercher.php?
mot=location#res"
origin:"http://www.toutjavascript.com"
pathname:"/reference/chercher.php"
port:""
protocol:"http:"
search:"?mot=location"

En plus de ces propriétés, location propose la méthode reload(), qui


n’attend aucun paramètre, pour forcer le rechargement de la page
en cours.
La méthode replace() change la page en cours de consultation par
l’URL passée en paramètre.

10.1.6 L’historique de navigation

Le sous-objet history de window permet de naviguer par


programmation dans l’historique de consultation, en simulant les
boutons [Précédent] et [Suivant] du navigateur.
Les trois méthodes de navigation sont :
Méthode de navigation Effet
Retourne d’une page en arrière
back()
dans l’historique
Avance d’une page en avant
forward()
dans l’historique
Se déplace de nb pages dans
l’historique.
go(Integer nb) Ainsi, location.go(-1) est
équivalent à location.back() et
location.go(1) à
location.forward()
Si la navigation n’est pas possible parce qu’il n’y a pas d’historique
correspondant à la demande, aucune action n’est réalisée.

Pour des raisons de confidentialité, l’objet history ne donne pas


accès à l’historique de navigation mais permet seulement de
naviguer en avant ou en arrière.

Depuis HTML5, et l’usage des appels AJAX pour modifier le


contenu de la page sans la recharger entièrement, il est possible
d’ajouter des entrées dans l’historique, avec pushState(), et de
modifier des entrées avec replaceState().

10.1.7 L’impression

La méthode print() ouvre la boîte de dialogue de paramétrage de


l’impression. L’utilisateur peut configurer son impression et la valider.
Le JavaScript ne peut heureusement pas lancer une impression
sans une confirmation de l’utilisateur.
Les propriétés de styles CSS permettent d’optimiser les impressions
de vos pages via une règle @media. Il est ainsi possible de cacher
certains éléments de la page qui n’ont pas d’utilité une fois imprimés,
comme des menus de navigation.
La propriété @media peut s’utiliser directement dans une balise
<style> :
@media print {
/* appliqué uniquement pour l'impression */
#menu {
display:none;
}
}

Elle peut aussi s’ajouter dans un appel à un fichier de CSS externe :


<link media="print" href="impression.css" />

10.1.8 La position et les dimensions de la fenêtre

L’objet window possède des méthodes pour redimensionner et


déplacer une fenêtre de navigateur sur l’écran.
Le redimensionnement se réalise avec resizeTo() selon la syntaxe
générique :
window.resizeTo(Integer largeur, Integer hauteur)

Il est aussi possible de modifier la taille de la fenêtre par rapport à


ses dimensions actuelles avec resizeBy() selon la syntaxe :
window.resizeTo(Integer deltaLargeur, Integer deltaHauteur)

Des méthodes semblables modifient la position de la fenêtre.


La méthode moveTo() attend en paramètre les coordonnées
précises en pixels :
window.moveTo(Integer gauche, Integer haut)
En JavaScript, les positions se mesurent à partir du coin
supérieur gauche de la fenêtre. Le premier paramètre est souvent
nommé left (« gauche ») et indique la position horizontale. Le
second paramètre est nommé top (« haut ») et indique la position
verticale à partir du haut et vers le bas de l’écran.

La méthode moveBy() déplace la fenêtre du nombre de pixels


passés en paramètres :
window.moveTo(Integer deplaceHorizontal, Integer deplaceVertical)

De manière générale, il n’est pas recommandé d’utiliser ces


méthodes sans une bonne raison : l’utilisateur n’aime pas être
dérangé dans ses habitudes de travail. Un site qui abuse des
effets sur les fenêtres ne sera pas apprécié.

L’objet window possède aussi des propriétés, accessibles


uniquement en lecture, pour obtenir les coordonnées de la position
actuelle de la fenêtre sur l’écran. Malheureusement, les propriétés
ne sont pas universelles et dépendent du navigateur. Il faut par
conséquent tester leur existence pour obtenir la position dans tous
les cas. Les propriétés screenLeft, screenX ou posX donnent la
position horizontale. Les propriétés screenTop, screenY et posY
donnent la position verticale. Pour tester l’existence d’une propriété
d’un objet, la syntaxe est très simple :
if (monObjet.maPropriete) { /* la propriété existe */ }

Voici donc la fonction qui retourne la position de la fenêtre :


function getWinPos() {
var pos={x:0, y:0};
if (window.screenLeft) pos.x=window.screenLeft;
if (window.screenTop) pos.y=window.screenTop;
if (window.screenX) pos.x=window.screenX;
if (window.screenY) pos.y=window.screenY;
if (window.posX) pos.x=window.posX;
if (window.posY) pos.y=window.posY;
return pos;
}

Retrouvez le script à l’adresse http://tjs.ovh/winpos.


La fonction getWinPos() retourne un objet avec les propriétés x et y
correspondant à la position de la fenêtre.

Dans le cas d’appareils mobiles, smartphones et tablettes, la


position et la dimension des fenêtres de navigateur sont fixées
par le système d’exploitation. Les propriétés de ce paragraphe ne
sont donc utiles que si l’utilisateur travaille sur un poste fixe
classique.

10.1.9 Le scroll dans la fenêtre

Le JavaScript permet, par programmation, de forcer le défilement


(scroll en anglais) des ascenseurs vertical et horizontal.
La méthode scrollTo() force le défilement à une position fixée en
pixels :
window.scrollTo(Integer positionHorizontale, Integer positionVerticale)

La méthode scrollBy() déplace les ascenseurs par rapport à la


position actuelle :
window.scrollBy(Integer deplaceHorizontal, Integer deplaceVertical)

Il n’est pas recommandé de créer des pages nécessitant un


scroll horizontal. Ainsi, les appels se font le plus souvent avec
un premier paramètre à 0, sous la forme suivante :
window.scrollTo(0, 200).

_ 10.2 MANIPULATION DES POP-UP


10.2.1 Ne pas utiliser les pop-up

Les pop-up, qu’on pourrait traduire par « fenêtres surgissantes », ne


doivent pas être utilisés. Ils sont presque systématiquement bloqués
par les navigateurs, pour protéger les utilisateurs. Les pop-up ont été
surexploités par les éditeurs de sites web et les plateformes
publicitaires pour présenter des contenus non sollicités par les
utilisateurs. Les navigateurs ont depuis le début des années 2000
appris à les détecter et à les supprimer avant même leur ouverture.
Nous allons néanmoins dans ce paragraphe découvrir comment
configurer et ouvrir un pop-up et détecter si le navigateur ne l’a pas
détruit.

10.2.2 Configurer et ouvrir un pop-up

L’ouverture d’un pop-up est déclenchée par l’appel à la méthode


open() de window. La méthode retourne un nouvel objet de type
Window qui permet de manipuler le pop-up ouvert :
Window window.open(String url, String name, String option)

La méthode open() attend trois paramètres :


url est l’adresse de la page à charger dans le pop-up.
name est le nom du pop-up. Toute nouvelle ouverture d’un pop-
up de même name remplacera le pop-up précédent. La
propriété name est semblable à la propriété target d’une balise
<a>.
option est une chaîne de caractères définissant les options
d’affichage du pop-up.
Les options d’ouverture adoptent un format particulier et limité aux
pop-up :
option1=valeur1,…,optionN=valeurN

Voici un tableau des différentes options et valeurs possibles pour la


chaîne de configuration d’un pop-up :
Option Signification Valeur possible
width Largeur en pixels Nombre entier
heigh Hauteur en pixels Nombre entier
Position horizontale en
left Nombre entier
pixels
top Position verticale en pixels Nombre entier
scrollbars Affichage des ascenseurs yes|no|auto
Affichage de la barre de
directories yes|no
raccourcis
Affichage de la barre de
menubar yes|no
menus
Affichage de la barre
location yes|no
d’adresse
Autorisation de
resizable yes|no
redimensionnement

10.2.3 Détecter l’existence d’un pop-up

Grâce au retour d’un objet de type Window à l’ouverture du pop-up,


on peut tester facilement l’existence de la fenêtre.

Les navigateurs détruisent les pop-up qui sont ouverts


automatiquement (après une minuterie par exemple), sans une
intervention volontaire de l’utilisateur. Habituellement, un pop-up
créé lors d’un clic n’est pas bloqué. Cela permet d’afficher par
exemple une fenêtre d’aide sur le clic d’un pictogramme.

Pour tester l’existence du pop-up, il suffit de tester l’existence de la


valeur de retour de open() :
if (winPopup) { /* Le pop-up existe bien */ }
Une fois l’existence du pop-up vérifiée, les méthodes focus() et blur()
permettent de le faire apparaître au premier plan ou au contraire de
le cacher sous les autres fenêtres des logiciels affichés à l’écran.

Les pop-up de type site-under sont des fenêtres placées sous le


navigateur actuel. L’idée est que cette fenêtre cachée sera visible
quand l’utilisateur fermera son navigateur. Il aura alors une
nouvelle occasion de consulter ce contenu, le plus souvent
publicitaire.

La propriété opener contient l’objet de type Window qui est à


l’origine du pop-up. Grâce à ce lien vers la fenêtre appelante, il est
possible de communiquer entre les différentes fenêtres.
La méthode close() ferme le pop-up. Dans ce cas, la propriété
closed de la fenêtre passe à true.
Un exemple de manipulations de pop-up est disponible à l’adresse
http://tjs.ovh/popup.

_ 10.3 MANIPULATION DES FRAMES


10.3.1 Ne pas utiliser les frames

Les frames (« cadres » en français) ne sont plus utilisées dans la


construction des sites modernes. Nous n’aborderons donc pas leur
usage dans cet ouvrage.

10.3.2 Le cas des iframes

Si les frames ont été abandonnées, les iframes (qu’on pourrait


comprendre comme des frames intégrées) sont toujours très
utilisées, en particulier pour présenter du contenu venant de services
ou de sites externes. Nous allons donc détailler ici leur utilisation et
leurs limites, imposées par la sécurisation des données de
navigation.
Une iframe est créée par la balise HTML <iframe>, qui reçoit de
nombreux attributs dédiés :
src contient l’adresse de la page à afficher dans l’iframe.
scrolling vaut yes ou no ou auto.
frameborder contient la largeur de bordure de la frame.
allowtransparency contient true ou false.
L’avantage d’utiliser une iframe est triple :
Le rendu et le chargement de la page principale ne sont pas
bloqués pendant l’appel au contenu de l’iframe. Cet appel de
type asynchrone a donc peu d’impact sur les performances du
site principal.
Une iframe d’un domaine différent de la page principale est
appelée dans une enclave indépendante. Une iframe externe
ne peut donc pas accéder, par défaut, au contenu de la page
appelante, ni aux données de l’utilisateur.
Le contenu de l’iframe n’est pas intégré à la page appelante et
n’a pas d’impact sur son référencement.

◆ La sécurité des iframes

Grâce à la same-origin policy (« politique d’origine identique »)


implémentée dans les navigateurs, les iframes garantissent à la
page appelante :
L’intégrité du contenu affiché.
La sécurité et la confidentialité des données.
La same-origin policy restreint l’accès aux ressources chargées
depuis une autre origine. La notion d’origine identique est très
stricte. Deux pages ont la même origine si et seulement si elles
utilisent le même protocole, les mêmes domaine et sous-domaine et
le même port de communication. Si le navigateur détecte une
tentative d’accès depuis une origine différente, il l’interdira et
déclenchera une erreur bloquante de type « Blocked a frame from
accessing a cross-origin frame ».
Les iframes sont donc très souvent utilisées pour afficher des
éléments publicitaires (via des régies comme Google Adsense ou
des serveurs de publicité comme Smart AdServer). Elles sont
également utilisées pour afficher du contenu de services externes
comme les boutons Like de Facebook ou les derniers tweets d’un
compte Twitter.

Les régies publicitaires vendent aux annonceurs à la fois de


l’espace et des données. Elles sont en recherche permanente de
nouvelles données concernant les emplacements et les visiteurs
touchés. Certaines n’hésitent pas à essayer de contourner les
sécurités des iframes par des scripts cachés et sophistiqués pour
obtenir le maximum d’informations, voire déposer des cookies.

Même si les appels à ces services externes se présentent la plupart


du temps sous la forme d’un JavaScript, une iframe est créée.
Utilisez l’explorateur d’éléments de la console du navigateur pour
observer les iframes ajoutées à la page.

◆ Manipuler les iframes

Créons une iframe dans notre page avec la syntaxe suivante :


<iframe src="iframe1.html" width="200" height="150" frameborder="0" scrolling="no"
id="iframe1" />

L’objet de type Window correspondant à la frame peut être accédé


par le tableau frames[], qui contient aussi les iframes :
var f=window.frames[0];

La frame est également accessible par son élément HTML et la


propriété contentWindow qui pointe vers l’objet Window :
var f=document.getElementById("iframe1").contentWindow;
Avec l’objet window présent dans la variable f, il est possible de
manipuler le contenu et l’apparence de la frame. Rendez-vous sur la
page http://tjs.ovh/iframe pour des interactions détaillées entre la
page et son iframe.

◆ L’en-tête X-Frame-Options

Le serveur web peut ajouter l’option X-Frame-Options dans l’en-tête


HTTP de la page pour indiquer comment elle doit se comporter
quand elle est appelée dans une iframe. Les valeurs possibles de
l’option sont :
DENY, pour interdire toute inclusion de la page dans une
iframe.
SAMEORIGIN, pour l’autoriser uniquement sur le même
domaine.
ALLOW-FROM http://website.com/, pour autoriser une origine
différente précise.

Attention, l’en-tête HTTP est construit par le serveur web soit


directement par des directives Apache, soit par programmation
avec par exemple la fonction PHP :
header(option, valeur)

Cette option permet de maîtriser la façon dont votre site peut être
embarqué dans d’autres sites.
DEUXIÈME PARTIE

L’interactivité

Après une première partie consacrée à quelques rappels de


bonnes pratiques en programmation et aux bases du langage
JavaScript, il est temps d’aborder la partie interactivité avec
les utilisateurs.
Dans cette partie, nous allons découvrir comment réagir aux
différents événements survenant sur la page, grâce au
puissant gestionnaire d’événements du langage.
Nous apprendrons aussi à manipuler le contenu du
document, en identifiant et modifiant des éléments HTML ou
en en ajoutant de nouveaux dans le corps de la page.
Le chapitre sur les formulaires nous permettra de créer des
interfaces interactives pour que les utilisateurs puissent
envoyer des informations vers le serveur.
Nous découvrirons aussi les appels AJAX en mode
asynchrone pour réaliser des actions simultanées sans
ralentir les interactions des visiteurs.
Même si la gestion des erreurs peut paraître superflue, nous
verrons qu’elle est indispensable pour réaliser des
applications fiables et robustes dans toutes les situations.
Nous pourrons stocker des informations sur l’appareil de
l’utilisateur pour l’identifier et sauver des préférences.
Enfin, nous irons encore plus loin dans le langage avec la
géolocalisation des utilisateurs, l’envoi de notifications et
même le tracé de graphiques 2D interactifs.
11

La programmation
événementielle

Objectif
Dans ce premier chapitre de cette seconde partie, nous allons
aborder la programmation événementielle, qui est au cœur de
l’interactivité du web. Nous verrons comment détecter les
événements de la page et comment déclencher des actions
associées.

_ 11.1 PROGRAMMATION
ÉVÉNEMENTIELLE
Le JavaScript est un langage événementiel, c’est-à-dire qu’il détecte
les événements qui surviennent sur la page, y réagit et déclenche de
nouveaux traitements. Un événement en programmation est un
changement d’état qui survient sur la page. Les types d’événements
sont très variés :
Le mouvement de souris.
Le clic sur un élément.
L’appui d’une touche du clavier.
Le chargement d’un élément de la page.
La rotation du smartphone en mode paysage.
Le redimensionnement de la fenêtre.
La perte de connexion Internet.
Si la programmation par événements est indispensable pour créer
de l’interactivité, elle implique également d’observer une rigueur
dans la conception. Les événements sont susceptibles de survenir à
tout moment, ou de ne pas se produire du tout. L’ordre des
événements et des actions prévu par le développeur a aussi toutes
les chances de ne pas être respecté par l’utilisateur. Des
événements parasites peuvent aussi se déclencher, comme un
chargement d’élément critique ralenti ou une coupure de connexion.
Il faut donc s’assurer que la fonctionnalité d’une page est cohérente,
même si l’utilisateur oublie ou inverse des étapes ou si la page subit
des lenteurs de chargement.
La programmation événementielle s’oppose dans le principe à la
programmation séquentielle :
Programmation
Programmation événementielle
séquentielle
Traitements
Ordre des événements inconnu
parfaitement séquencés
Traitements
Survenue d’un événement non garantie parfaitement
prédictibles
Traitements parasites
Événement parasite possible
impossibles

11.1.1 Détecter les événements

Comme nous l’avons souvent constaté dans les chapitres


précédents, le JavaScript autorise plusieurs syntaxes. La détection
d’événements n’échappe pas à la règle.

◆ La détection dans le code HTML


Historiquement, la détection d’événements et la programmation du
traitement associé se réalisent directement dans le code HTML de
l’élément à surveiller. En ajoutant un attribut du type onEvenement,
le navigateur déclenchera le traitement JavaScript défini au
déclenchement de l’événement.
Ainsi, en ajoutant l’attribut onclick sur une balise div, le code
JavaScript défini dans l’attribut est exécuté, dès que le clic est
détecté :
<div onclick="console.log('Click sur le Div')">
Cliquez-moi
</div>

Notez que la norme HTML impose d’écrire les noms de balises et


d’attributs en minuscules. Même si onClick est plus lisible et tout
aussi fonctionnel, la syntaxe onclick doit être utilisée.

Le code JavaScript défini dans l’attribut onEvenement se comporte


comme du JS classique. Comme le point-virgule sépare les
instructions, il est possible d’enchaîner plusieurs traitements :
<div onclick="compteur++; console.log(compteur);">Compter</div>

Si le traitement est vraiment lourd, il est recommandé de le déporter


dans une fonction :
<div onclick="compterClick()">Compter</div>

À cause du délimiteur de chaîne de caractères utilisé pour définir le


contenu de l’attribut d’un événement, la programmation peut parfois
être délicate. Vous devez utiliser l’apostrophe pour délimiter les
chaînes de caractères internes :
<div onclick="console.log('Click sur le Div')">

Le caractère d’échappement antislash, utilisé pour les chaînes de


caractères JavaScript, n’est pas reconnu en HTML. La syntaxe
suivante déclenchera une erreur au clic :
<div onclick="console.log(\"Click sur le Div\")">
En effet, le navigateur tentera d’exécuter le contenu de l’attribut
onclick qui contient simplement console.log(\.

Notez que la norme HTML impose de définir le contenu des


attributs avec des guillemets. Bien que la syntaxe
onevenement='instruction;' soit fonctionnelle, il ne faut pas
l’utiliser.

À l’intérieur de l’événement, l’objet this est une référence sur


l’élément qui a détecté l’événement. Ainsi, il est possible d’utiliser
this pour afficher son identifiant dans la console et changer la
couleur de fond du div cliqué en rouge :
<div onclick="console.log(this.id); this.style.background='#f00'" id="monDiv">
Cliquez-moi
</div>

Nous verrons au chapitre suivant à partir d’ici sur la manipulation


du document comment aller bien plus loin dans l’interactivité.

Cette manière de déclarer les événements par attribut n’est plus


vraiment utilisée aujourd’hui. Si elle est très rapide, elle présente des
inconvénients :
Le traitement JavaScript est mélangé à la mise en forme des
données via HTML.
Le JavaScript n’est pas centralisé, ni facile à retrouver.
Les événements sont déclarés avant la fin du chargement de la
page.

◆ La détection par onEvent

Les éléments HTML possèdent des propriétés de type onevent qui


permettent de définir des fonctions associées aux événements en
utilisant la syntaxe :
element.onevent=function(evt) { /* Traitements */ }

Par exemple pour détecter l’événement onload du document, nous


pouvons écrire :
La détection window.onload=function(evt) {
console.log("Document chargé");
};

◆ La détection par gestionnaire d’événements

Pour répondre aux inconvénients de la déclaration d’événements par


attributs sur les balises HTML, le JavaScript propose un gestionnaire
d’événements puissant qui :
ne modifie pas le code HTML et garde la séparation entre
traitements et données ;
centralise la déclaration des événements dans un bloc
JavaScript ;
détermine le moment opportun pour déclarer les événements,
par exemple après le chargement de fichiers externes
indispensables ;
n’interfère pas avec les événements définis par d’autres
fonctionnalités.
La méthode addEventListener() sur un élément HTML de la page
enrichit le gestionnaire d’événements selon la syntaxe générique
suivante :
element.addEventListener(String myEvent, Function ToDo, [JSON options])

La méthode attend trois paramètres :


myEvent, une chaîne de caractères qui définit le type
d’événement à détecter.
toDo(), une fonction qui reçoit en paramètre l’objet de type
Event détecté.
Le paramètre facultatif options est un objet JSON qui définit
plus précisément le comportement de l’événement.
Reprenons notre exemple de détection du clic sur un élément div et
déclarons le gestionnaire d’événements associé :
<div id="myDiv">
Cliquez-moi
</div>
<script type="text/javascript">
var myDiv=document.getElementById("myDiv");
myDiv.addEventListener("click", clickDiv);
function clickDiv(evt) {
console.log("Click sur "+evt.currentTarget.id);
evt.currentTarget.style.background="#f00";
}
</script>

Immédiatement, nous remarquons que le code nécessaire pour


afficher le nom du div cliqué et forcer la couleur de fond en rouge
lors du clic est bien plus long.
La variable myDiv est initialisée avec l’élément HTML ayant myDiv
comme attribut id.
La méthode addEventListener() ajoute au gestionnaire
d’événements l’écoute de l’événement (d’où le terme listener) de
type click sur myDiv et y associe la fonction clickDiv().
La fonction clickDiv() reçoit en paramètre evt un objet de type Event.
La propriété currentTarget de evt contient une référence vers myDiv.
On peut ainsi afficher l’identifiant dans la console et changer le style
de fond.
Un objet Event possède deux propriétés de cible :
currentTarget référence l’objet sur lequel l’événement a été
attaché.
target référence l’objet sur lequel l’événement s’est produit.

Notez que les noms d’événements dans le gestionnaire ne sont


pas préfixés par on comme c’est le cas dans la déclaration par
attributs de balises HTML.
_ 11.2 LE GESTIONNAIRE
D’ÉVÉNEMENTS EN DÉTAIL
11.2.1 L’objet Event

Un objet de type Event est passé à la fonction de traitement liée à la


détection de l’événement. Cet objet contient toutes les informations
concernant l’événement déclencheur.
Nous avons déjà vu la propriété target qui référence l’objet sur lequel
a été détecté l’événement.
La propriété type est une chaîne de caractères avec le type
d’événement détecté.
La propriété timestamp contient le nombre de millisecondes
écoulées depuis l’initialisation du document.
Les événements Event possèdent également plusieurs propriétés
booléennes déterminant leur comportement :
Bubbles indique si l’événement remonte dans la hiérarchie des
éléments du document, à l’image d’une bulle d’air dans l’eau.
cancelBubble indique si la remontée dans le DOM (Document
Object Model pour « modèle objet du document ») est annulée
par la méthode stopPropagation().
cancelable indique si l’action par défaut est annulable par la
méthode prevent Default().
defaultPrevented indique si l’action par défaut a été annulée.
Observons le processus de remontée des événements dans le
document en créant un div nommé container contenant deux div
nommés click1 et click2 :
<div id="container">
<div id="click1">Cliquez sur div#click1</div>
<div id="click2">Cliquez sur div#click2</div>
</div>
Déclarons dans le gestionnaire d’événements la détection des clics
sur les éléments body, container et click1 et click2 :
var container=document.getElementById("container");
var click1=document.getElementById("click1");
var click2=document.getElementById("click2");
document.body.addEventListener("click", function(evt) {console.log("Listener de
body")});
container.addEventListener("click", function(evt) {console.log("Listener de
container")});
click1.addEventListener("click", clickDiv1);
click2.addEventListener("click", clickDiv2);

function clickDiv1(e) {
console.log("Click1 : bubbles="+e.bubbles+" cancelBubble="+e.cancelBubble);
}
function clickDiv2(e) {
e.stopPropagation();
console.log("Click2 : bubbles="+e.bubbles+" cancelBubble="+e.cancelBubble);
}

Avec un peu de CSS, non reporté dans cette page, le navigateur


affiche :

Retrouvez ce script exécutable à l’adresse http://tjs.ovh/bubble.


Quand on clique sur le premier div, le gestionnaire d’événements
détecte le clic et déclenche la fonction clickDiv1(). L’événement se
propage en remontant dans les éléments de niveaux supérieurs,
d’abord container, puis body. La console contient alors :
Click1 : bubbles=true cancelBubble=false
Listener de container
Listener de body
Un clic sur le second div déclenche la fonction clickDiv2() qui
interrompt la propagation de l’événement e avec stopPropagation().
La console n’affiche que :
Click2 : bubbles=true cancelBubble=true
La propriété cancelBubble est bien passée à true.
Si vous cliquez sur container, le clic est bien sûr détecté et remonte
jusque body. La console affiche alors :
Listener de container
Listener de body

Notez que l’ordre de déclaration des événements n’a pas d’impact


sur le comportement du script à l’exécution.

11.2.2 L’héritage de Event

Tous les types d’événements ne sont pas identiques. Le JavaScript


utilise la notion d’héritage pour créer des objets adaptés à chaque
situation. Reprenons notre fonction getHeritage() du chapitre sur la
programmation objet pour déterminer la chaîne d’héritages de
quelques événements sur la fenêtre du navigateur :
window.addEventListener("load", getInfoEvt);
window.addEventListener("click", getInfoEvt);
window.addEventListener("keypress", getInfoEvt);
function getInfoEvt(evt) {
console.log("Listener de l'événement '"+evt.type+"'");
console.log(getHeritages(evt));
}

Après le chargement de la page, un clic de la souris et une frappe au


clavier, la console affiche :
Listener de l'événement 'load'
▶ (2) ["Event", "Object"]
Listener de l'événement 'click'
▶ (4) ["MouseEvent", "UIEvent", "Event", "Object"]
Listener de l'événement 'keypress'
▶ (4) ["KeyboardEvent", "UIEvent", "Event", "Object"]
Sans surprise, les événements descendent de l’objet Object de plus
bas niveau. Les événements d’interactivité ont une chaîne plus
longue qui passe par le type UIEvent (pour User Interface Event),
puis un type encore plus précis selon l’origine de l’événement avec
MouseEvent ou KeyboardEvent.
Nous verrons à partir d’ici de ce chapitre comment gérer
précisément la position de la souris et la saisie au clavier.

11.2.3 Ajout d’événements multiples

Il faut bien noter que l’appel à la méthode addEventListener() ajoute


un nouveau détecteur d’événements au gestionnaire et ne remplace
pas l’événement existant. Ce fonctionnement peut sembler
perturbant, mais il est en fait indispensable pour ne pas supprimer
des traitements prévus par d’autres fonctionnalités.
Prenons le cas très classique du onload qui détecte la fin de
chargement de la page. De nombreux scripts et modules externes
(en particulier les frameworks comme jQuery, que nous détaillerons
ici) doivent s’assurer que le document est bien chargé avant de
lancer leurs traitements. Grâce au fonctionnement de
addEventListener(), les traitements nécessaires aux autres modules
ne sont pas affectés. Ajoutons trois détections de l’événement load
sur la fenêtre et observons le résultat :
window.addEventListener("load", function(evt) {console.log("Listener 1")});
window.addEventListener("load", function(evt) {console.log("Listener 2")});
window.addEventListener("load", onLoad);
function onLoad(evt) {
console.log("Listener 3 via onLoad()");
}

Sans surprise, la console affiche dans l’ordre :


Listener 1
Listener 2
Listener 3 via onLoad()
Notez ici la déclaration en fonctions anonymes des deux premières
détections d’événements.

11.2.4 Suppression d’événements

Pour supprimer des événements du gestionnaire, il faut utiliser la


méthode opposée removeEventListener() qui attend également deux
paramètres :
element.removeEventListener(String myEvent, Function toDo)

Cette syntaxe supprime uniquement l’événement de type myEvent


associé à la fonction toDo().
Ainsi, pour supprimer le troisième détecteur de l’événement load, il
faut impérativement reprendre la fonction d’appel associée :
window.removeEventListener("load", onLoad);

Au chargement de la page, la console ne contient plus que les deux


premières lignes :
Listener 1
Listener 2
La suppression d’événements déclarés avec une fonction anonyme
n’est pas possible.

11.2.5 Liste des types d’événements

Il existe des dizaines de types d’événements disponibles en


JavaScript. Certains ne sont associés qu’à des types particuliers
d’éléments. Par exemple, un formulaire possède l’événement submit
qui détecte la validation et la soumission du formulaire. Les
événements disponibles dépendent également des navigateurs et du
type d’appareils utilisés. Un navigateur de smartphone va gérer les
mouvements tactiles, ce que ne prévoit pas un navigateur classique.
Comme les objets du JavaScript possèdent des propriétés du type
onEvent, il est possible de repérer tous les événements d’un type
d’objet. Nous allons donc créer un script qui détecte l’ensemble des
types d’événements.
/* Construction de la liste des éléments */
var htmlElement=Object.create(HTMLElement.prototype);
var elements=[window, document, htmlElement, document.body];
/* Tableau contenant les résultats */
var evenements=[];
/* Fonction d'analyse d'un élément */
function getEvents(objet) {
var id=Object.getPrototypeOf(objet).constructor.name;
evenements[id]=[];
var n=0;
for (var i in objet) { /* Parcours des propriétés */
/* Recherche des propriétés commençant par on */
if (i.substring(0, 2)=="on") {
evenements[id][n]=i;
n++;
}
}
}
/* Parcours de tous les éléments à traiter */
for (var i=0; i<elements.length; i++) {
getEvents(elements[i]);
}
console.log(evenements);

La première partie sert à remplir le tableau des éléments à analyser.


Notez l’usage de Object.create() (vu ici) pour créer un objet de type
HTMLElement.
La fonction getEvents() recherche les événements parmi les noms
des propriétés commençant par on.
À l’exécution, le script affiche dans la console le tableau des
éléments avec la liste des événements trouvés pour chacun d’eux :
Retrouvez ce script en ligne sur http://tjs.ovh/events.

11.2.6 Forcer un événement

Il peut être utile de forcer par programmation la survenue d’un


événement, sans attendre une action de l’utilisateur. Prenons
l’exemple de la soumission du formulaire monForm. La syntaxe
classique pour soumettre un formulaire est d’utiliser la méthode
submit() associée :
document.monForm.submit()

Certains types d’événements ne sont pas déclenchables par les


méthodes natives des objets. Le survol de la souris en fait partie. Il
faut donc passer par la création d’un objet de type Event :
var evt=new Event("mouseover");

L’objet evt n’est pas encore assigné à un élément. Sa propriété


target vaut donc null. Pour affecter l’événement, il faut utiliser la
méthode dispatchEvent() de l’élément :
document.getElementById("divMouse").dispatchEvent(evt);

Dès que cet appel est déclenché, le gestionnaire d’événement le


détecte et lance le traitement prévu. L’objet evt est maintenant
affecté à une target.

11.2.7 Gérer la souris

Les événements faisant intervenir la souris sont des objets de type


MouseEvent ou WheelEvent, contenant des propriétés
supplémentaires par rapport à l’objet Event de plus bas niveau :
button indique par un chiffre le numéro bouton de souris cliqué.
which indique également le numéro de bouton pressé.
clientX et clientY ou pageX et pageY indiquent les coordonnées
dans le document.
offsetX et offsetY indiquent les coordonnées dans l’élément
HTML qui reçoit l’événement.
screenX et screenY indiquent les coordonnées sur l’écran de
l’appareil.
movementX et movementY indique le déplacement de la souris
depuis le dernier MouseEvent reporté.
Il existe de nombreux types d’événements en rapport avec les
mouvements de souris :
onmouseenter détecte l’entrée au-dessus d’un élément.
onmouseleave détecte la sortie de l’élément.
onmouseover détecte également le passage de souris au-
dessus d’un élément.
onmouseout détecte également la sortie de l’élément.
onmousemove détecte un mouvement de souris.
onmousewheel détecte un mouvement de molette de la souris.
Sans oublier, bien sûr, les événements liés directement aux clics de
souris :
onmousedown détecte que le bouton de souris est pressé.
onmouseup détecte que le bouton de souris est relâché, après
mousedown.
onclick détecte le clic simple de souris, après onmouseup.
oncontextmenu détecte l’ouverture du menu contextuel,
souvent appelé par un clic droit.
ondblclick détecte le double-clic.
onauxclick détecte le clic auxiliaire.

◆ Position et coordonnées du curseur


Même si de nombreux événements de souris sont gérés nativement
par le navigateur, il est utile de bien comprendre les différents
comportements possibles, ainsi que les coordonnées du pointeur
dans la page.
Les coordonnées du curseur souris sont exprimées dans plusieurs
référentiels. Voici un exemple montrant les différentes propriétés.
bloc.addEventListener("mousemove", blocMove);
function blocMove(evt) {
var i="Position dans l'écran : (screenX, screenY) = ";
i+="("+evt.screenX+", "+evt.screenY+")<br />";
i+="Position dans la page : (clientX, clientY) = ";
i+="("+evt.clientX+", "+evt.clientY+")<br />";
i+="Position dans le bloc : (offsetX, offsetY) = ";
i+="("+evt.offsetX+", "+evt.offsetY+")<br />";
i+="Déplacement depuis mousemove : (movementX, movementY) = ";
i+="("+evt.movementX+", "+evt.movementY+")";
info.innerHTML=i;
}

Le script affiche les différentes positions de souris retournées par


l’événement mousemove. Voici une capture d’écran avec le pointeur
souris :
Retrouvez ce script à la page http://tjs.ovh/mouse.
La position dans l’écran a pour origine le point (0, 0) en haut à
gauche de l’écran. La position (clientX, clientY) a pour origine le coin
supérieur gauche du navigateur. La position (offsetX, offsetY).
Le déplacement de la souris entre deux événements mousemove
est donné par (movementX, movementY).

◆ Entrée et sortie d’éléments

Pour détecter le survol et la sortie d’un élément de la page par la


souris, il faut utiliser :
bloc.addEventListener("mouseenter", function(evt) {
info.style.borderColor="#0a0";
});
bloc.addEventListener("mouseleave", function(evt) {
info.style.borderColor="#999";
});

Ces deux listeners changent la couleur du bloc info quand la souris


passe sur le bloc gris.

Notez que l’événement mouseover peut souvent être remplacé


par une simple déclaration CSS avec la propriété :hover.

◆ Gestion des clics

Ajoutons la détection des clics sur notre exemple :


bloc.addEventListener("click", function(evt) {
console.log("Clic détecté (bouton="+evt.button+" which="+evt.which+")");
});
bloc.addEventListener("dblclick", function(evt) {
console.log("Double-clic (bouton="+evt.button+" which="+evt.which+")");
});
bloc.addEventListener("auxclick", function(evt) {
console.log("Clic aux détecté (bouton="+evt.button+" which="+evt.which+")");
});

Les événements de clics possèdent naturellement les propriétés de


position. Les propriétés button et which identifient les boutons
cliqués :
Clic détecté (bouton=0 which=1)
Clic auxiliaire détecté (bouton=1 which=2)
Le clic droit est détecté par l’événement contextmenu. En
empêchant l’apparition du menu contextuel avec preventDefault(),
l’événement click est détecté :
bloc.addEventListener("contextmenu", function(evt) {
evt.preventDefault();
});

Lors d’un clic droit, le menu contextuel n’apparaît plus, mais la


console affiche maintenant :
Clic auxiliaire détecté (bouton=2 which=3)
Il n’est pas recommandé de modifier sans raison le comportement
par défaut du navigateur. La suppression du menu contextuel doit
être réalisée uniquement dans des cas d’applications qui le méritent,
sans gêner l’utilisation normale du navigateur.

◆ Gestion de la souris sur les appareils tactiles

Le traitement des événements de souris paraît à la fois riche,


puissant et finalement assez simple. Pourtant, il ne faut pas oublier
les utilisateurs d’appareils tactiles, c’est-à-dire sans souris. Des tests
poussés sont nécessaires pour garantir une bonne ergonomie à la
fois sur ordinateur classique et sur mobile ou tablette.
N’hésitez pas à exécuter les scripts http://tjs.ovh/events et
http://tjs.ovh/mouse sur un appareil muni d’un écran tactile pour
obtenir les nouveaux événements dédiés et le comportement des
événements de souris.
Nous étudierons les événements de souris liés au drag and drop
dans le chapitre suivant.

11.2.8 Gérer le clavier

Contrairement à une idée intuitive, la gestion du clavier ne se limite


pas aux saisies dans les formulaires. Tous les éléments du
document peuvent détecter les interactions avec le clavier, rendant
ainsi possible la création de jeux ou de navigation rapide par le
clavier.
Les événements en rapport avec le clavier (keyboard en anglais)
sont du type Keyboard Event. Ils comportent des propriétés
particulières destinées à identifier les saisies :
altKey vaut true si la touche [alt] est active au moment de la
saisie.
ctrlKey vaut true si la touche [ctrl] est active au moment de la
saisie.
shiftKey vaut true si la touche [shift] est active au moment de la
saisie.
metaKey vaut true si la touche dédiée au système d’exploitation
([cmd] pour macOS ou [win] pour Windows) est active au
moment de la saisie.
code contient le code de la touche sur le clavier.
key contient la chaîne de caractères correspondant à la touche.
keyCode et charCode contiennent le code numérique de la
touche.
repeat vaut true si la touche est maintenue enfoncée dans une
série de répétitions.
L’appui sur une touche du clavier déclenche trois événements
successifs :
onkeydown détecte qu’une touche est enfoncée.
onkeypress détecte qu’une touche a bien été pressée.
onkeyup détecte qu’une touche est relâchée.
Ce script affiche dans la console l’enchaînement des trois
événements de clavier détectés sur l’objet window et les
caractéristiques de la touche pressée :
window.addEventListener("keydown", kDown);
window.addEventListener("keyup", kUp);
window.addEventListener("keypress", kPress);
function kDown(evt) {
console.log("La touche ["+evt.code+"] est enfoncée");
}
function kUp(evt) {
console.log("La touche ["+evt.code+"] est relachée");
}
function kPress(evt) {
console.log("La touche ["+evt.code+"] a été tapée :");
console.log(" Valeur = "+evt.key);
console.log(" charCode = "+evt.charCode);
console.log(" [alt] = "+evt.altKey);
console.log(" [ctrl] = "+evt.ctrlKey);
console.log(" [shift] = "+evt.shiftKey);
console.log(" [meta] = "+evt.metaKey);
}

Un appui sur la touche [a] d’un clavier Azerty déclenche l’affichage


suivant dans la console :
La touche [KeyQ] est enfoncée
La touche [KeyQ] a été tapée :
Valeur = a
charCode = 97
[alt] = false
[ctrl] = false
[shift] = false
[meta] = false
La touche [KeyQ] est relachée
Le code de la touche vaut KeyQ, comme pour un clavier Qwerty. La
valeur est bien « a », qui correspond au code ASCII 97, retrouvé
avec l’appel de String.fromCharCode(97).
Les touches utilitaires du clavier comme les touches de fonction ou
les flèches ne déclenchent pas l’événement keypress. La console
affiche simplement les événements keydown et keyup :
La touche [ArrowUp] est enfoncée
La touche [ArrowUp] est relachée
Retrouvez ce script en ligne à l’adresse http://tjs.ovh/clavier.
Nous verrons des usages utiles de ces événements dans le chapitre
des formulaires en créant des contrôles de saisies.

11.2.9 Gérer les erreurs

Les erreurs sont des événements particuliers au sein du navigateur.


Nous verrons au chapitre du traitement des erreurs le traitement
d’un événement erreur JavaScript.
12

Manipuler le document

Objectif
Dans ce chapitre, nous étudierons toutes les manipulations
du document. Nous verrons comment trouver les éléments
HTML par leur balise, leur nom ou leur classe CSS, comment
modifier leur style, leur position et leur apparence. Nous
verrons aussi comment ajouter dynamiquement du contenu
dans la page.

_ 12.1 LE HTML DYNAMIQUE


12.1.1 Un peu de préhistoire web

Le Dynamic HTML (DHTML) consiste à manipuler les éléments du


document pour le rendre interactif. Dans les années 2000, cette
manipulation était très fastidieuse car chaque famille de navigateurs
(avec en particulier Internet Explorer, Netscape, Firefox) avait créé
ses propres fonctions d’accès et de traitements. La moindre
fonctionnalité nécessitait plusieurs branches de code pour assurer la
compatibilité entre navigateurs. Cette fragmentation est à l’origine de
l’essor de nombreux frameworks, de plus en plus utilisés aujourd’hui
et que nous aborderons dans la troisième partie de ce livre, à partir
d’ici.
Grâce aux travaux de ECMAScript, la situation s’est largement
améliorée. La syntaxe est maintenant unifiée. Il reste malgré tout
quelques difficultés, en particulier au niveau du calcul des
dimensions et du positionnement des éléments. Entre-temps, de
nombreuses typologies d’appareils sont apparues, avec des
caractéristiques souvent opposées, allant des écrans minuscules
des montres connectées aux écrans géants à la définition 4K des
téléviseurs. Des tests poussés sont toujours nécessaires pour
valider vos développements.

12.1.2 Les possibilités de HTML

La programmation HTML en mode dynamique, associée au


gestionnaire d’événements du navigateur, autorise la construction de
véritables applications interactives et embarquées dans un simple
navigateur. Parmi les possibilités, nous allons étudier dans la suite
de ce chapitre comment :
trouver un élément dans le document ;
cacher et montrer un élément ;
modifier le contenu d’un élément ;
modifier le style CSS d’un élément ;
ajouter de nouveaux éléments ;
gérer le drag and drop.

_ 12.2 TROUVER LES ÉLÉMENTS


DU DOCUMENT
12.2.1 L’objet document

L’objet document supporte l’ensemble des éléments HTML de la


page. Il est l’objet racine du DOM qui donne accès à toutes les
méthodes de recherche, de manipulation et de création des
éléments HTML.
Le DOM (Document Object Model pour « modèle objet du
document ») est une organisation hiérarchisée en arborescence de
l’ensemble des éléments du document. Chaque élément forme un
nœud (node) dans le document. La structure du DOM est donnée
naturellement par le code HTML correspondant. Prenons l’exemple
de ce document HTML simpliste :
<body>
<h1>Comprendre le DOM</h1>
<div id="container">
<p>Paragraphe 1</p>
<div id="bloc">
<span class="span">Bloc</span>
<p class="para">Paragraphe A dans bloc</p>
<p class="para">Paragraphe B dans bloc</p>
<p class="para paraBold">Paragraphe C dans bloc</p>
</div>
</div>
</body>

La structure DOM correspondante apparaît dans l’onglet [Elements]


de la console du navigateur. Ici, le dernier paragraphe du div#bloc
est sélectionné. Dans la barre de statut, l’arborescence des nœuds
est affichée :

Dans le navigateur, le rendu (avec quelques ajouts CSS non


reportés dans le livre) est le suivant :
Retrouvez cette page à l’adresse http://tjs.ovh/dom.

12.2.2 Les objets de type HTMLElement

Chaque élément HTML possède un constructeur associé à sa


balise. Par exemple, un élément div est du type HTMLDivElement,
body du type HTMLBodyElement ou une image img du type
HTMLImageElement. Ce constructeur définit les propriétés
nécessaires au fonctionnement de la balise.
Tous les éléments sont issus de la même chaîne d’héritages :
Object, le type de plus bas niveau.
EventTarget, qui permet d’utiliser le gestionnaire d’événements.
Node, qui correspond à un nœud dans le DOM.
Element, qui définit un élément dans le DOM.
HTMLElement, qui définit un élément avec une balise HTML.
Pour simplifier la lecture, nous utilisons généralement le raccourci
HTMLElement pour le type des objets du document.
De la même manière, document est un objet de type
HTMLDocument qui hérite de Object, EventTarget et Node.
Pour identifier rapidement la chaîne d’héritages d’un élément,
utilisez notre fonction getHeritages() créée ici du chapitre sur la
programmation objet. Elle retourne le tableau de tous les
constructeurs de l’objet passé en paramètre. Ainsi,
getHeritages(document.body) retourne Array(6) [
"HTMLBodyElement", "HTMLElement", "Element", "Node",
"EventTarget", "Object" ].

Grâce aux propriétés et aux méthodes des éléments, nous allons


pouvoir les manipuler, les déplacer, changer leur style CSS, affecter
des événements, modifier leur contenu.

12.2.3 Les méthodes de recherche d’éléments

◆ Les méthodes d’accès par propriétés

Au fil des exemples de ce livre, nous avons utilisé régulièrement la


méthode get ElementById() de document. Cette méthode retourne le
premier élément HTML du DOM ayant l’attribut id passé en
paramètre. La syntaxe générique est la suivante :
HTMLElement document.getElementById(String identifiant)

Il existe d’autres méthodes de recherche sur le document qui


retournent une liste d’éléments. Notez que le nom des méthodes
commence par getElements au pluriel pour bien marquer le retour
sous forme de tableau d’éléments.
La méthode getElementsByClassName() retourne une liste
d’éléments dont la classe CSS correspond au nom de classe passé
en paramètre :
HTMLCollection document.getElementsByClassName(String nomClasse)

La méthode getElementsByName() retourne une liste d’éléments


dont l’attribut name correspond au nom passé en paramètre :
HTMLCollection document.getElementsByClassName(String nom)
La méthode getElementsByTagName() retourne une liste d’éléments
dont la balise correspond à la balise passée en paramètre :
HTMLCollection document.getElementsByTagName (String balise)

Le type HTMLCollection est une sorte de tableau particulier


simplifié qui n’hérite pas du type Array. Ainsi, les méthodes de
tableau comme forEach() ou map() ne sont pas disponibles. Il faut
absolument utiliser une boucle classique pour parcourir
l’ensemble des éléments retournés, de l’indice 0 à l’indice length.

Prenons quelques appels de ces méthodes :


console.log(document.getElementsByTagName("p"));
console.log(document.getElementsByClassName("para"));
console.log(document.getElementsByTagName("img"));
console.log(document.getElementById("monID"));

La console affiche les éléments trouvés dans des tableaux


HTMLCollection :
HTMLCollection(4) [p, p.para, p.para, p.para.paraBold]
HTMLCollection(3) [p.para, p.para, p.para.paraBold]
HTMLCollection []
null
La première ligne recherche tous les éléments de balise p.
La seconde ligne recherche tous les éléments ayant la classe para.
Les éléments ayant d’autres classes associées sont aussi retournés.
Le troisième appel retourne un tableau vide, car aucune image n’est
présente dans le document.
Le dernier appel retourne null, car aucun élément d’identifiant monID
n’est présent.
Notez que les éléments trouvés sont affichés dans la console par
leur sélecteur CSS. Le dernier paragraphe du document <p
class="para paraBold"> est affiché avec p.para.paraBold. Cette
représentation nous amène à deux autres méthodes de recherche.
◆ L’accès par sélecteur CSS

L’accès par sélecteur CSS utilise la notation CSS pour atteindre les
éléments du document.
La méthode querySelectorAll() retourne tous les éléments répondant
au sélecteur CSS passé en paramètre :
NodeList document.querSelectorAll(String selecteurCSS)

La méthode querySelector() retourne le premier élément trouvé dans


le DOM répondant au sélecteur passé en paramètre :
NodeList document.querSelector (String selecteurCSS)

La méthode querySelector() est en fait un raccourci de l’appel à la


méthode querySelectorAll()[0].

Le type NodeList retourné par la méthode querySelectorAll() est


une sorte de tableau, encore différent de HTMLCollection. Les
navigateurs récents supportent les méthodes de parcours de type
forEach() sur NodeList. Mais les plus anciens et Internet Explorer
ne le supportent pas. Pour être certain d’avoir un retour
manipulable sans erreur, il est nécessaire de transformer le type
NodeList en type Array. Nous verrons l’astuce d’écriture ici
(§ Ajouter des événements).

Prenons quelques exemples de recherche par sélecteur :


console.log(document.querySelectorAll("span.span"));
console.log(document.querySelectorAll("div#bloc"));

La console affiche :
NodeList(3) [p.para, p.para, p.para.paraBold]
NodeList [div#bloc]
Notez que les méthodes de recherche d’éléments HTML sont
aussi disponibles depuis d’autres éléments HTML. Dans ce cas,
la recherche ne se déclenche pas sur l’ensemble du document,
mais seulement dans les nœuds internes à l’élément. Ainsi,
l’appel à
document.getElementById("bloc").getElementsByTagName("p")
ne retourne que les trois paragraphes du bloc d’identifiant bloc
HTMLCollection(3) [p.para, p.para, p.para.paraBold]

◆ Accès directs

Certains éléments sont accessibles nativement par la notation


pointée depuis l’objet document :
document.body est équivalent au premier élément de balise
body trouvé avec document.getElementsByTagName("body")
[0].
document.forms est un tableau HTMLCollection de tous les
éléments de balise form trouvés dans la page.
document.scripts est un tableau HTMLCollection de tous les
éléments de balise script trouvés dans la page.

◆ Accéder aux éléments enfants

Un élément possède la propriété childNodes, qui contient un tableau


NodeList de tous les éléments enfants directs.
Attention, la liste des enfants tient compte précisément de l’écriture
du code. Les espaces et les retours à la ligne de mise en forme du
code HTML deviennent des enfants text dans childNodes. Ainsi, la
liste des enfants de l’élément div#bloc s’affiche dans la console
avec :
console.log(document.querySelector("div#bloc").childNodes)

La console contient alors :


NodeList(9)
0: text
1: span.span
2: text
3: p.para
4: text
5: p.para
6: text
7: p.para.paraBold
8: text
length: 9
Pour n’obtenir que les balises utiles dans la liste des enfants, il faut
supprimer tous les caractères de mise en forme du code HTM. La
liste childNodes devient :
NodeList(4) [span.span, p.para, p.para, p.para.paraBold]
0: span.span
1: p.para
2: p.para
3: p.para.paraBold
length: 4
Les propriétés firstChild et lastChild pointent respectivement sur le
premier et le dernier élément de la liste childNodes.

◆ Accéder aux éléments proches

Les propriétés nextSibling et previousSibling pointent vers l’élément


précédent ou suivant de même niveau hiérarchique. L’utilité de ces
propriétés est claire quand on sait que le mot sibling signifie « enfant
de mêmes parents ».
Attention, comme pour toutes les propriétés et méthodes de
recherche dans le DOM, si aucun résultat ne correspond, null est
retourné. Pour éviter une erreur à l’exécution, il est recommandé
de tester que l’élément existe avant de lui appliquer un traitement.

Pour remonter d’un niveau dans la hiérarchie des objets, la propriété


parentElement contient l’élément parent.

◆ Le bon moment pour l’accès aux éléments

Le JavaScript étant un langage événementiel (via les actions de


l’utilisateur) et asynchrone (à cause de chargements volontairement
ou aléatoirement retardés), l’état du document n’est pas connu à
l’avance.
Il faut donc s’assurer que le document est complètement construit
pour chercher à accéder aux éléments. Un appel lancé trop tôt pour
rechercher un élément retournera la valeur null et déclenchera une
erreur s’il est manipulé.
Le document possède la propriété readyState, qui indique l’état
d’avancement de sa construction en trois étapes :
"loading" indique que le document est en cours de construction
et de chargement des données.
"interactive" indique que le document est complètement
construit et à l’écoute des actions de l’utilisateur. Cet état
correspond à l’événement DOMContentLoaded.
"complete" indique que la page est complètement chargée,
avec les feuilles de style et les fichiers JS externes. Cet état
correspond à l’événement onload de body.
L’événement onreadystatechange détecte le changement de valeur
de la propriété readyState. Grâce à cet événement, il est possible de
coder les actions voulues n’importe où dans le script. L’événement
se charge de déclencher les actions dès que le document est prêt.
Dans ce script, nous suivons la valeur de la propriété readyState au
cours de l’avancement dans le code HTML. Un gestionnaire
d’événement détecte la progression du statut du document. Le
changement de valeur de readyState à interactive est le moment où
des actions sur les éléments de la page sont possibles.
<html>
<head>
<title>Etat du document avec readyState</title>
<script type="text/javascript">
document.addEventListener("readystatechange", function(evt) {
console.log("readyState passe à "+document.readyState);
if (document.readyState=="interactive") {
document.getElementById("monH1").style.border="1px solid #999";
}
});
</script>
</head>
<body>
<script>
console.log("Dans le body readyState="+document.readyState);
</script>
<h1 id="monH1">Avancement de l'état du document avec readyState</h1>
</body>
<script>
console.log("Avant /html readyState="+document.readyState);
</script>
</html>

La console contient l’enchaînement des valeurs de readyState :


Dans le body readyState=loading
Avant /html readyState=loading
readyState passe à interactive
readyState passe à complete
Retrouvez ce script à l’adresse http://tjs.ovh/readystate.
Notez qu’un script déclaré et exécuté en fin de code HTML, après
la balise </body>, peut garantir que le document est bien dans un
état interactive, si aucun fichier .js externe n’est appelé en mode
asynchrone.

_ 12.3 MANIPULER LES ÉLÉMENTS


Nous avons appris à trouver les éléments de la page avec les
méthodes de recherche. Voyons toutes les manipulations
disponibles, en partant de la page décrite par ce code HTML :
<body>
<h1 id="monH1" class="titre">Manipulation des <strong>éléments</strong></h1>
<div id="container">
<p>Paragraphe 1</p>
<div id="bloc">
<span class="span">Bloc</span>
<p class="para">Paragraphe A dans bloc</p>
<p class="para">Paragraphe B dans bloc</p>
<p class="para paraBold">Paragraphe C dans bloc</p>
</div>
</div>
<div id="dernier">Encore un div</div>
</body>

12.3.1 Modifier le contenu

La propriété innerHTML des éléments est accessible en lecture et en


écriture. Elle donne accès au contenu du code HTML à l’intérieur de
l’élément :
console.log(document.getElementById("monH1").innerHTML);

La console contient l’intérieur de la balise h1 :


Manipulation des <strong>éléments</strong>
La propriété outerHTML contient le code HTML de l’élément en
incluant sa propre balise, avec ses attributs :
console.log(document.getElementById("monH1").outerHTML);

La console contient :
<h1 id="monH1" class="titre">Manipulation des
<strong>éléments</strong></h1>
La propriété innerText contient le texte du contenu débarrassé du
code HTML :
console.log(document.getElementById("monH1").innerText);

La console contient uniquement le texte :


Manipulation des éléments

12.3.2 Ajouter des événements

Nous avons déjà vu comment ajouter des événements au


gestionnaire d’événements dans le chapitre dédié. La méthode
addEventListener() est très souvent employée dans un projet web.
Jusqu’à maintenant, nous avons surtout employé la méthode de
recherche nommée getElementById(), qui ne retourne qu’un seul
élément. Voyons comment affecter le même événement à une liste
d’éléments.
Imaginons que vous souhaitiez détecter le clic sur tous les
paragraphes du document. La recherche utilisera indifféremment
getElementsByTagName("p") ou querySelectorAll("p"), qui
retournent une liste d’éléments :
var paras=document.querySelectorAll("p");

Nous pouvons les manipuler avec une boucle for classique :


for (var i=0; i<paras.length; i++) {
paras[i].addEventListener("click", function(evt) {
console.log("Clic détecté sur "+evt.target);
});
}

Nous pouvons aussi convertir la liste en un tableau Array et utiliser


ainsi la méthode forEach(). Commençons par la conversion :
paras=Array.prototype.slice.call(paras);
console.log(paras);
console.log(Array.isArray(paras));

La console confirme bien que paras est maintenant un tableau :


(4) [p, p.para, p.para, p.para.paraBold]
true
La conversion utilise les particularités de la programmation objet de
type prototype du JavaScript. La syntaxe Array.prototype.slice.call()
lance un appel, via call(), de la méthode slice() du constructeur
Array.
La méthode call() (voir détails ici) exécute la fonction slice() avec la
liste paras d’origine.
La méthode slice() (voir détails ici) retourne un nouveau tableau à
partir de la liste en entrée.
Après cette conversion, paras est un tableau qui supporte la
méthode forEach() :
paras.forEach(function(p) {
p.addEventListener("click", function(evt) {
console.log("Clic détecté sur "+evt.target+" (déclaration via forEach)");
});
});

Après cette double déclaration d’événements, un clic sur un


paragraphe affiche deux lignes dans la console :
Clic détecté sur [object HTMLParagraphElement]
Clic détecté sur [object HTMLParagraphElement] (déclaration via
forEach)
12.3.3 Accéder aux attributs

◆ Les attributs simples

Le JavaScript dispose de méthodes pour accéder aux attributs d’un


élément HTML de type HTMLElement.
La méthode hasAttribute() retourne true si l’attribut passé en
paramètre est présent dans la balise :
Bool element.hasAttribute(String nomAttribut)

La méthode getAttribute() retourne le contenu de l’attribut passé en


paramètre :
String element.getAttribute(String nomAttribut)

La méthode setAttribute() modifie le contenu d’un attribut :


String element.getAttribute(String nomAttribut, String valeur)

Ces quelques lignes de code détectent si l’élément dernier possède


l’attribut title. Si title n’est pas présent, il est ajouté et initialisé :
var dernier=document.getElementById("dernier");
if (!dernier.hasAttribute("title")) {
dernier.setAttribute("title", "Nouvel attribut title");
}
console.log(dernier.getAttribute("title"));
console.log(dernier.outerHTML);

La console contient alors :


Nouvel attribut title
<div id="dernier" title="Nouvel attribut title">Encore un div</div>

◆ Les attributs de données

La norme HTML du W3C définit strictement les attributs et les noms


autorisés par balise. Le développeur peut malgré tout stocker
directement dans le DOM des données via des attributs. Pour
respecter la norme HTML, les noms des attributs doivent
commencer par data-, suivi d’un nom au choix. Par exemple, pour
stocker dans un attribut le nombre de clics reçus sur chaque élément
p du document, nous pouvons écrire un événement qui va alimenter
l’attribut data-nb-clics :
var paras=document.querySelectorAll("p");
for (var i=0; i<paras.length; i++) {
/* Initialisation de la propriété pour tous les paragraphes */
paras[i].setAttribute("data-nb-clics", "0");
paras[i].addEventListener("click", function(evt) {
/* Conversion de l'attribut en entier */
var nb=parseInt(this.getAttribute("data-nb-clics"));
/* Affectation du nouveau nombre de clics */
this.setAttribute("data-nb-clics", nb+1);
/* Mise à jour de la date du dernier clic */
this.setAttribute("data-last-clic", new Date().getTime());
});
}

Ce script initialise l’attribut avec data-nb-clics="0" pour tous les


éléments du tableau paras. Une nouvelle détection de clics est
ajoutée. Elle récupère la chaîne de caractères de l’attribut, la
convertit en entier, l’augmente d’une unité et l’affecte de nouveau à
l’attribut. Ensuite, un nouvel attribut data-last-clic est renseigné avec
la date du dernier clic, exprimé avec getTime(), soit le nombre de
secondes écoulées depuis le 1er janvier 1970.
L’avantage de respecter le nommage des attributs personnels avec
le préfixe data- est qu’ils sont tous regroupés dans la propriété
dataset (ensemble de données). Cette propriété facilite grandement
la manipulation des données grâce à sa structure.
Après quelques clics sur le premier paragraphe de notre exemple,
sa propriété outer HTML vaudra :
<p data-nb-clics="3" data-last-
clic="1527930243304">Paragraphe 1</p>
Et sa propriété dataset sera un objet de type DOMStringMap :
DOMStringMap {nbClics: "3", lastClic: "1527930243304"}

Notez que tous les attributs sont des chaînes de caractères. Si


vous devez effectuer des opérations mathématiques, il faut
convertir les données dans le bon format.

La structure DOMStringMap est un objet JSON classique qui se


manipule avec la notation pointée.
L’instruction suivante :
console.log(document.querySelectorAll("p")[0].dataset.nbClics)

affiche dans la console :


3
La propriété dataset est accessible en lecture et en écriture. Ainsi,
pour réinitialiser les informations de clics sur notre premier
paragraphe, nous pouvons écrire :
var data=document.querySelectorAll("p")[0].dataset;
data.nbClics="0";
delete data.lastClic;
document.querySelectorAll("p")[0].dataset=data;

Le dataset est récupéré dans la variable data. Le nombre de clics


est réinitialisé à 0. L’opérateur delete supprime l’élément lastClics.
Le nouveau dataset est affecté au paragraphe. À l’issue de ces
quelques lignes, la balise vaut :
<p data-nb-clics="0">Paragraphe 1</p>
N’oubliez pas que la norme HTML impose d’écrire les noms des
attributs en minuscules. Ainsi data-nbClics n’est pas
recommandé. Le nom data-nb-clics est conforme à la norme.
Attention, le nom de la propriété de dataset est ici transformé,
comme les noms de propriétés CSS, en nbClics.

12.3.4 Manipulations de CSS

◆ Récupérer le style d’un élément

La méthode getComputedStyle() de window retourne le style CSS


de l’élément passé en paramètre, calculé (computed) par le
navigateur à partir des classes affectées à cet élément :
CSSStyleDeclaration window.getComputedStyle(HTMLElement monElement)

Pour le titre de la page d’exemple :


console.log(getComputedStyle(document.getElementById("monH1")));

La console affiche un objet complet de type CSSStyleDeclaration


contenant toutes les propriétés de style par ordre alphabétique et
leur valeur :
CSSStyleDeclaration {0: "animation-delay", 1: "animation-
direction", 2: "animation-duration", 3: "animation-fill-mode", 4:
"animation-iteration-count", 5: "animation-name", 6: "animation-
play-state", 7: "animation-timing-function", 8: "background-
attachment", 9: "background-blend-mode", 10: "background-clip",
11: "background-color", …}

◆ Manipuler le style

Toutes les propriétés de style d’un élément sont accessibles via


l’objet style. Les propriétés CSS sont ensuite disponibles en lecture
et en écriture selon la nomenclature CSS. Comme les noms de
variables ne peuvent pas contenir le caractère tiret, les noms de
style CSS sont transformés en JavaScript, avec une simple règle : le
tiret est supprimé et la lettre suivante passe en majuscule. Ainsi, la
propriété CSS background-color devient en JS backgroundColor.
Ajoutons un nouvel événement à chacun des éléments div, qui va
détecter le clic pour changer quelques propriétés CSS :
var divs=document.getElementsByTagName("div");
for (var i=0; i<divs.length; i++) {
divs[i].addEventListener("click", function(evt) {
this.style.backgroundColor="#000";
this.style.color="#fff";
});
}

Un clic sur un div le passe en fond noir et écriture blanche. Notez le


paramètre this pour identifier le div cliqué.

◆ Ajouter et supprimer des classes CSS

L’usage direct des propriétés de style n’est pas une approche idéale.
L’utilisation de classes CSS est préférable et facilite les évolutions :
pour modifier l’apparence à l’issue d’un événement, aucune mise à
jour de code JavaScript n’est nécessaire, seule la classe CSS sera à
corriger.
La propriété className d’un élément contient la chaîne de
caractères de l’attribut class :
console.log(getComputedStyle(document.getElementById("monH1")));

La console contient :
para paraBold
La propriété classList d’un élément contient une sorte de tableau
DOMTokenList des classes CSS. Cette structure est plus simple à
manipuler, en particulier lorsque plusieurs classes sont utilisées
simultanément :
console.log(document.querySelector("p.para.paraBold").classList);

La console contient un objet de type DOMTokenList avec les deux


classes :
DOMTokenList( 2)
0: "para"
1: "paraBold"
length: 2
value: "para paraBold"

La structure DOMTokenList sert à représenter un ensemble de


données (token) séparées par un espace, comme les classes
dans l’attribut class d’une balise. Comme pour les autres
structures de données proches d’un Array, certains navigateurs
n’acceptent pas les méthodes classiques de manipulation comme
forEach(). Il est donc recommandé d’utiliser une boucle for
classique ou la syntaxe de conversion vers un tableau, vue ici.

12.3.5 La position et les dimensions

Le JavaScript donne accès à la taille et à la position des éléments


via la méthode getBoundingClientRect(), qui retourne un objet de
type DOMRect :
DOMRect element.getBoundingClientRect()

Le type DOMRect contient les propriétés suivantes :


width, la largeur en pixels de l’élément, en incluant les marges
et les bordures.
height, la hauteur en pixels de l’élément, en incluant les marges
et les bordures.
top et left, la position du coin supérieur gauche par rapport à la
page.
right et bottom, la position du coin inférieur droit par rapport à la
page.
Nous verrons en fin de chapitre, un exemple d’utilisation concrète de
ces propriétés de positions.

12.3.6 Animations via CSS

Même si l’étude du CSS n’est pas l’objet de ce livre, nous allons


aborder ici le principe des animations via CSS. Il est possible
d’animer des éléments de la page avec du JavaScript pur mais,
grâce aux optimisations des navigateurs, l’utilisation du CSS
améliore la fluidité et facilite également la programmation. Retrouvez
d’autres astuces CSS dans l’annexe.

◆ Définir une animation

Une animation est simplement une transition entre des étapes de


mise en forme. La déclaration d’une animation nécessite :
Une définition des étapes dans un bloc CSS @keyframes.
Une définition des modalités de transition entre les étapes.
Le bloc @keyframes permet de définir deux ou plusieurs étapes.
L’avancement des étapes est exprimé en pourcentage. L’étape
initiale from commence à 0 %, l’étape finale to vaut 100 %. Des
étapes intermédiaires facultatives peuvent être ajoutées.
La syntaxe générale d’un bloc @keyframes est :
@keyframes nomKeyFrame {
from { /* Définitions CSS de l'étape initiale */}
to { /* Définitions CSS de l'étape d'arrivée */}
}

Les animations CSS utilisent les propriétés préfixées par


animation- :
animation-name contient le nom du bloc d’étapes @keyframes.
animation-duration indique la durée nécessaire pour réaliser la
transition entre l’étape from et l’étape to.
animation-iteration-count indique le nombre de répétitions de
l’animation. Avec la valeur infinite, la répétition ne s’arrête
jamais.
animation-delay définit un délai de retard pour lancer
l’animation.
animation-direction définit le sens de l’animation. La valeur par
défaut est normal. La valeur reverse inverse les étapes de
début et de fin. La valeur alternate effectue un effet de va-et-
vient.
animation-timing-function indique la fonction d’accélération du
temps. La valeur par défaut est ease (« naturel » en français),
qui simule les mouvements naturels de la vie courante. La
valeur linear n’ajoute aucun effet. Des effets d’accélération et
de ralenti sont disponibles avec les valeurs, ease-in, ease-out,
ease-in-out.
Le langage CSS est suffisamment souple pour accepter une syntaxe
abrégée du type :
animation: name duration iteration-count timing-function

◆ Déclencher une animation

Habituellement, une classe CSS est définie afin de déclencher


l’animation. Pour lancer l’animation sur un élément, il suffit de lui
affecter cette classe, en utilisant la propriété className.
Définissons une animation de clignotement de bordure et de couleur
de fond qui pourrait servir à matérialiser une erreur de saisie :
@keyframes clignoter {
from {
border-color:#fff;
background-color:#fff;
}
to {
border-color:#f00;
background-color:#ffa;
}
}
.clignotement {
animation: clignoter 0.2s alternate infinite;
}

Nous définissons quelques zones :


<div class="zone" id="z1">Div Zone 1</div>
<div class="zone" id="z2">Div Zone 2</div>
<div class="zone" id="z3">Div Zone 3</div>
<div class="zone" id="z4">Div Zone 4</div>

La propriété className permet d’ajouter la classe de clignotement à


un élément :
document.getElementById("z1").className+=" clignotement";

La première zone clignote rapidement à l’écran :

Retrouvez le script de cette animation à l’adresse


http://tjs.ovh/animation.
Un autre exemple d’animation est disponible dans l’exemple complet
de fin de chapitre.

◆ Événements liés aux animations

Le JavaScript permet de détecter les étapes d’une animation grâce à


trois événements de type AnimationEvent dont les noms sont
parlants :
animationstart détecte le lancement d’une animation sur un
élément.
animationend détecte la fin de l’animation.
animationiteration détecte une nouvelle itération de
changement d’état.
Le type AnimationEvent hérite de Event et possède les propriétés
supplémentaires suivantes :
animationName contient le nom du @keyframes utilisé.
elapsedTime est le temps écoulé depuis le lancement de
l’animation.

Notez que les animations ne sont pas une fin en soi. Elles doivent
amener de la valeur ajoutée pour l’utilisateur et ne pas le gêner.
Par exemple, des animations trop lentes, qui ralentissent les
actions des utilisateurs, sont à proscrire.

_ 12. 4 CRÉER DE NOUVEAUX


ÉLÉMENTS

12.4.1 La méthode createElement()

Le JavaScript autorise la création et l’ajout d’éléments HTML par


programmation. La première étape est de créer un nouvel
HTMLElement avec la méthode createElement() de document :
HTMLElement document.createElement(String typeElement)

Le paramètre typeElement est une chaîne désignant la balise


correspondant à l’élément à créer. Une fois l’élément créé, il faut y
ajouter les attributs avec la fonction setAttribute().
Par exemple, pour créer l’élément div#notification, on écrit :
var notification=document.createElement("div");
notification.setAttribute("id", "notification");
notification.innerHTML="Voici notre bloc de notification";
notification.className="fixed top right";

L’élément créé a comme équivalent HTML, retourné par sa propriété


outerHTML :
<div id="notification" class="fixed top right">Voici notre bloc de notification</div>

12.4.2 Ajouter des éléments dans le document

À ce moment, notre div n’est pas encore sur le document. Il n’est


qu’un objet JavaScript en mémoire. La seconde étape est donc de
l’ajouter au bon endroit du DOM. Il existe deux méthodes d’ajout
d’un élément enfant à un élément parent :
La méthode appendChild() ajoute l’élément enfant à la fin d’un
élément parent :
parent.appendChild(HTMLElement enfant)

La méthode insertBefore() autorise l’insertion de enfant dans


l’élément parent, juste avant l’élément position :
parent.insertBefore(HTMLElement enfant, HTMLElement position)

Il n’existe pas de méthode insertAfter(), car il est possible de la créer


avec la propriété nextSibling qui contient l’élément suivant dans la
liste des enfants.
Pour ajouter notre notification dans le document, utilisons
appendChild() sur body :
document.body.appendChild(notification);

Avec quelques classes CSS de mise en forme, nous obtenons un


bloc ombré en position fixée en haut à droite :
div#notification {
border:1px solid #999;
background: #ccc;
box-shadow: 3px 3px 3px #999;
font-weight: bold;
width:120px;
}
.fixed { position:fixed; }
.top { top:10px; }
.right { right:10px; }

Pour rendre les informations de la notification plus visibles, un effet


d’animation peut être efficace :
.animate {
animation: animFromRight 0.5s 1;
}
@keyframes animFromRight {
from { right: -150px; }
to { right: 10px; }
}

Visualisez l’effet d’animation de cet exemple à l’adresse


http://tjs.ovh/notif.

Notez que la propriété innerHTML (voir ici) permet aussi de


modifier le contenu de la page en injectant directement du code
HTML dans un élément.

12.4.3 Supprimer un élément HTML

La méthode removeChild() retire du DOM l’élément enfant de son


élément parent :
parent.childnodes(HTMLElement enfant)

Ainsi, pour supprimer la notification après 2 secondes, il suffit


d’utiliser le minuteur setTimeout() :
setTimeout(function() {
document.body.removeChild(document.getElementById("notification"));
}, 2000);
12.4.4 Un exemple complet d’info-bulles

Nous allons réutiliser toutes les notions vues dans ce copieux


chapitre et réaliser un exemple complet de script d’info-bulles, les
bulles d’informations qui s’affichent au survol de la souris d’un
élément.
La création des info-bulles se fait en deux étapes :
Dans le code HTML, ajout de la classe CSS infobulle aux
éléments concernés et affectation du texte à afficher dans
l’attribut title.
En JavaScript, lancement d’un script qui traite tous les éléments
de la classe infobulle, une fois que le document est prêt.

Ce mode de fonctionnement en deux étapes est très largement


adopté par tous les modules JavaScript que vous pouvez
télécharger sur Internet. Il permet de bien séparer le contenu à
afficher du traitement JavaScript.

L’objectif de notre script est que, au survol de la souris sur un


élément possédant la classe infobulle, l’info-bulle native du
navigateur soit remplacée par notre info-bulle personnalisée :
<p>Qu'est-ce qu'une <span class="infobulle definition" title="Message d'information
qui apparaît au survol de la souris. Le nom bulle vient de l'univers de la bande
dessinée.">info-bulle</span> ?</p>

L’affichage de l’info-bulle native par le navigateur

Notre info-bulle personnalisée


Le script commence par détecter quand le document est prêt. Un
parcours de l’ensemble des éléments de classe infobulle initialise
leur dataset avec les données de travail et définit les événements
mouseleave et mouseenter avec les fonctions d’affichage et de
fermeture des bulles :
document.addEventListener("readystatechange", function(evt) {
if (document.readyState=="interactive") {
/* Trouver et parcourir tous les éléments de classe infobulle */
var elts=document.getElementsByClassName("infobulle");
for (var i=0; i<elts.length; i++) {
elts[i].dataset.bulleId="";
/* Transformer l'attribut title en dataset title */
elts[i].dataset.title=elts[i].getAttribute("title");
elts[i].setAttribute("title", "");
/* Ajouter le gestionnaire d'événement sur le mouseenter */
elts[i].addEventListener("mouseenter", showBulle);
/* Ajouter le gestionnaire d'événement sur le mouseleave */
elts[i].addEventListener("mouseleave", hideBulle);
}
}
});

La fonction showBulle() reçoit en paramètre l’événement evt à


l’origine de son déclenchement. Si bulleId est vide, aucune bulle n’a
encore été ouverte pour cet élément. La fonction crée un élément
div, lui affecte un identifiant, ses classes CSS et le contenu, puis la
bonne position. L’identifiant de la bulle est stocké dans bulleId :
function showBulle(evt) {
var elt=evt.target;
/* Création de la bulle si elle n'existe pas déjà */
if (elt.dataset.bulleId!="") {
return false;
}
var bulle=document.createElement("div");
var id="IB"+new Date().getTime();
bulle.setAttribute("id", id);
bulle.className="infobulleJS infobulleAnimateIn";
bulle.innerHTML=elt.dataset.title;
/* Positionnement de la bulle */
var rect=elt.getBoundingClientRect();
bulle.style.top=rect.top+rect.height+5;
bulle.style.left=rect.left+Math.round(rect.width/2);
/* Enregistrement de l'id de la bulle dans dataset */
elt.dataset.bulleId=id;
/* Ajout de la bulle sur le document */
document.body.appendChild(bulle);
}

La fonction hideBulle() est plus simple. Elle récupère l’identifiant de


la bulle à effacer dans bulleId, lance l’animation CSS de disparition
par transparence et un minuteur qui, en fin d’animation, supprime
l’élément du DOM :
function hideBulle(evt) {
var bulle=document.getElementById(evt.target.dataset.bulleId);
var elt=evt.target;
bulle.className="infobulleJS infobulleAnimateOut";
setTimeout(function() {
document.body.removeChild(bulle);
elt.dataset.bulleId="";
}, 400);
}

Retrouvez ce script à l’adresse http://tjs.ovh/infobulle.


Vous noterez que des flèches réalisées en CSS ont été ajoutées
pour pointer l’objet à l’origine de l’apparition de l’info-bulle. Reportez-
vous à l’annexe CSS pour comprendre comment utiliser les pseudo-
éléments.
Naturellement, nous pourrions ajouter quelques améliorations :
Programmation entièrement objet, sans fonction externe.
Gestion de la position de la bulle en fonction des bords de la
fenêtre pour optimiser son affichage.
Gestion d’un délai de fermeture de la bulle après que la souris
quitte la zone.
Optimisation sur un appareil tactile sans curseur souris.
Une proposition de script info-bulle optimisé est disponible à
l’adresse http://tjs.ovh/ infobulle2.

12.4.5 Un exemple complet de tri de tableau


de données

L’accès dynamique au contenu de la page offre des avantages


ergonomiques évidents. Un script peut facilement trier en quelques
millisecondes un tableau de données présenté à l’utilisateur, par un
simple clic sur les titres des colonnes. Avec une approche
traditionnelle, le clic sur le titre de la colonne lancerait un appel au
serveur, qui générerait une page HTML avec le tableau dans le bon
ordre et l’enverrait au navigateur. Le temps d’apparition au visiteur
se compte alors en secondes, à cause des allers-retours client-
serveur supplémentaires.
Voyons comment manipuler les lignes d’un tableau avec les
fonctionnalités vues dans ce chapitre. Définissons notre tableau
avec la balise table. Les balises th identifient les en-têtes des
colonnes :
<table class="tritable">
<tr>
<th>Client</th>
<th>Commandes</th>
<th>Chiffre Affaires</th>
</tr>
<tr><td>Raymond</td><td class="nb">2</td><td class="nb">250</td></tr>
<tr><td>Gérard</td><td class="nb">1</td><td class="nb">150</td></tr>

</table>

Avec quelques classes CSS, voici le rendu du début du tableau dans


la page :
Notez qu’un élément table dans le DOM possède toujours la
structure tbody, même si elle n’est pas déclarée en HTML. Ainsi, la
structure de ce tableau visible dans l’explorateur d’éléments du
navigateur commence bien par :

Certaines parties de code ne sont pas reproduites dans ce livre.


Retrouvez le code complet de ce script à l’adresse de démo
http://tjs.ovh/tritab.
Comme pour notre script info-bulles, nous allons détecter quand le
document est prêt et traiter tous les tableaux de classe tritable de la
page pour leur affecter les événements de clics sur les en-têtes :
document.addEventListener("readystatechange", function(evt) {
if (document.readyState=="interactive") {
/* Trouver et parcourir tous les tableaux de classe tritable */
var elts=document.querySelectorAll("table.tritable");
for (var i=0; i<elts.length; i++) {
elts[i].dataset.tritable=true;
/* Rechercher toutes les en-têtes de l'élément i */
var ths=elts[i].querySelectorAll("th");
for (var j=0; j<ths.length; j++) {
/* Ajout de l'icone indiquant la possibilité de tri */
ths[j].innerHTML+=" <i class=\"fas fa-sort light\"></i>";
ths[j].dataset.sens="";
/* Ajout de l'event click */
ths[j].addEventListener("click", triColonne, false);
}
}
}
});

Pour chaque tableau trouvé via le sélecteur CSS table.tritable, les


en-têtes sont parcourus par une boucle. Pour chaque en-tête de
balise th :
L’icône FontAwesome (voir le fonctionnement de cet outil dans
l’annexe CSS) fa-sort est ajoutée au contenu HTML.
La propriété data-sens est fixée à vide.
L’événement click déclenche la fonction triColonne().
Après cette initialisation, le tableau s’est enrichi des icônes indiquant
la possibilité de tri :

La fonction triColonne() se charge simplement de mettre à jour les


icônes de sens de tri de chaque colonne et de lancer le traitement
du tri via la fonction triTable().
La fonction triTable() utilise le tri à bulles pour faire remonter les
lignes une par une en fonction du sens de tri et de la colonne à trier.
Quand plus aucun mouvement n’est détecté, le tri est terminé. Voici
par exemple le début du tableau trié par noms de clients :

Encore une fois, un script n’est jamais terminé. Il peut toujours


être amélioré. Il faudrait prévoir de pouvoir trier des colonnes
contenant des dates ou la gestion de tableaux plus longs avec un
système de pagination.

_ 12.5 LE DRAG AND DROP


Le drag and drop (ou glisser-déposer) est maintenant parfaitement
exploitable dans les navigateurs. Le gestionnaire d’événements est
capable de détecter les opérations de glisser-déposer d’éléments à
l’intérieur de la page et même d’éléments externes, comme l’envoi
d’un document depuis l’explorateur de fichiers vers une zone
d’upload de fichiers.

12.5.1 Les événements drag and drop

Pour que le navigateur autorise le mouvement de drag and drop sur


un élément du document, il faut attribuer la valeur true à son attribut
draggable :
<div draggable="true">Football</div>

Les événements de glisser-déposer sont de type DragEvent, qui


hérite de la chaîne complète MouseEvent, UIEvent et Event.
Ces événements sont à séparer en deux groupes. Le premier
groupe s’applique sur l’élément déplacé par la souris :
drag détecte le début du mouvement sur l’élément d’origine.
dragstart détecte le début du mouvement sur l’élément déplacé.
dragend détecte la fin du drag and drop.
Le second groupe s’applique à la zone de dépose de l’élément :
dragover détecte le mouvement de déplacement sur une zone
de drop potentielle. Il faut obligatoirement ajouter cet
événement en empêchant le comportement par défaut du
navigateur pour que les événements suivants soient
correctement détectés.
dragenter détecte l’entrée de l’élément sur une zone de drop
autorisée.
dragleave détecte la sortie de l’élément de la zone de drop.
drop détecte la dépose de l’élément.
Les événements DragEvent possèdent la propriété dataTransfer, qui
contient les informations de données à transférer.
Imaginons une page de réservation avec une liste d’activités
disponibles. L’utilisateur choisit parmi les activités celles qu’il
souhaite pratiquer, en les déplaçant dans le bloc de droite :
Retrouvez le script de drag and drop d’activités à l’adresse
http://tjs.ovh/drag.
Un bloc d’activité est défini par son code HTML :
<div class="dragdrop" draggable="true" data-id="A1">Aqua-Poney</div>

Les différents événements nécessaires à la gestion et à l’ergonomie


de ce module ne sont pas reportés ici. Le script en démonstration,
avec le code source commenté, est disponible sur http://tjs.ovh/drag.

Naturellement, le but de ce script est bien de récupérer le résultat


des actions de l’utilisateur. Ici, il s’agit d’obtenir la liste des
activités choisies. Une fonction retourne un tableau des
identifiants data-id de tous les éléments du bloc de droite.

Retrouvez deux autres exemples de drag and drop d’éléments dans


le chapitre sur AJAX, et dans le chapitre des formulaires.

_ 12.6 LA SÉLECTION DE TEXTE


12.6.1 Les objets Selection et Range

Le JavaScript permet aussi d’interagir avec le texte sélectionné par


l’utilisateur. La sélection de contenu est retournée sous forme d’un
objet de type Selection par la méthode getSelection() de window :
Selection window.getSelection()
Pour obtenir le texte brut sélectionné, il suffit d’appeler la méthode
toString() :
window.getSelection().toString()

La sélection peut être manipulée grâce aux objets de type Range,


qui représentent une portion de document avec des nœuds et du
texte. Dans un objet Selection, le nombre de Range est indiqué par
la propriété rangeCount. La récupération d’un Range dans un objet
Selection se fait avec la méthode getRangeAt() :
Range window.getSelection().getRangeAt(Integer numero)

Le script contenant tous les exemples fonctionnels et commentés de


ce paragraphe est disponible à l’adresse http://tjs.ovh/selection.

12.6.2 La manipulation de Range

◆ Les propriétés de Range

Range est un objet complexe, avec des dizaines de propriétés et


méthodes, qui permet de manipuler le contenu de la page et de
produire, par exemple, des éditeurs de texte aussi complets que
Word, entièrement en ligne. Nous allons aborder ici les
fonctionnalités principales et communes à tous les navigateurs.
Un objet Range possède les propriétés suivantes, en lecture seule :
commonAncestorContainer pointe vers l’élément du DOM
commun.
startContainer pointe vers l’élément du DOM où commence la
sélection.
endContainer pointe vers l’élément du DOM où finit la sélection.
startOffset est un entier qui indique où démarre la chaîne de
texte du Range.
endOffset est un entier qui indique où se termine le texte du
Range.
collapsed est un booléen qui indique si la sélection est vide.
Imaginons le code HTML suivant qui servira de base à nos essais de
sélection :
<div class="container">
<div class="inline" id="inline1">Div de texte 1</div>
<div class="inline" id="inline2">Div de texte 2</div>
<div class="inline" id="inline3">Div de texte 3</div>
<div class="inline" id="inline4">Div de texte 4</div>
</div>

Prenons un exemple basique de sélection, sur le même élément


HTML :

Dans ce cas, les principales valeurs remarquables de Range sont :


window.getSelection().toString()=iv de texte
commonAncestorContainer=[object Text]
startContainer.id=inline2
startOffset=1
endContainer.id=inline2
endOffset=12
Avec un cas où plusieurs éléments sont regroupés dans la
sélection :

Les propriétés du Range sont :


window.getSelection().toString()=xte 1 Div de texte 2 Div de texte
commonAncestorContainer=[object HTMLDivElement]
startContainer.id=inline1
startOffset=9
endContainer.id=inline3
endOffset=12

◆ Les méthodes de Range

Voyons maintenant quelques méthodes de manipulation du Range :


insertNode(e) insère l’élément HTML e au début du Range.
surroundContents(e) entoure la sélection dans un nouvel
élément e.
deleteContents() supprime le contenu du Range.
setStart(i) modifie l’indice de départ à i.
setEnd(i) modifie l’indice de fin à i.
selectNodeContents(e) sélectionne l’élément e et l’affecte au
Range.
collapse() supprime la sélection.
Commençons par ajouter un élément HTML, ici une icône de l’outil
FontAwesome :
function addSmiley(evt) {
var smiley=document.createElement("i");
smiley.className="fas fa-smile";
console.log(smiley.outerHTML); // <i class="fas fa-smile"></i>
window.getSelection().getRangeAt(0).insertNode(smiley);
}

Pour entourer la sélection d’une balise, par exemple <strong>, il faut


utiliser la méthode surroundContents :
function addStrong(evt) {
var strong=document.createElement("strong");
window.getSelection().getRangeAt(0).surroundContents(strong);
}
Notez qu’une erreur est retournée si plusieurs éléments HTML
sont sélectionnés, car le code HTML correspondant à la
commande serait invalide. Il faut donc détecter une éventuelle
erreur avec try et catch, que nous verrons ici.

La méthode deleteContents() supprime du document la sélection


courante :
function deleteSelection(evt) {
window.getSelection().getRangeAt(0).deleteContents();
}

Grâce aux objets Range, il est possible de réaliser par


programmation des sélections et des manipulations. La méthode
createRange() de document crée un objet Range, qui peut ensuite
être manipulé. Par exemple, pour sélectionner la zone de texte
inline2, on écrit :
function selectZone2(evt) {
var range=document.createRange();
range.selectNodeContents(document.getElementById("inline2"));
window.getSelection().removeAllRanges(); /* Effacer la sélection en cours */
window.getSelection().addRange(range); /* Ajouter range à la sélection */
}

Sur un objet Selection, removeAllRanges() supprime les éléments


sélectionnés en cours et addRange() ajoute le Range passé en
paramètre à la sélection.

La manipulation de la sélection est une fonctionnalité avancée du


JavaScript, avec des particularités selon les navigateurs. Nous
verrons dans le chapitre sur les frameworks comment choisir les
modules externes, comme des éditeurs de texte enrichis.

12.6.3 Les événements de détection de sélection


Curieusement, il n’est pas facile de détecter une sélection de
contenu dans le document. Les événements onselectionstart et
onselectionchange sont bien disponibles, mais il n’existe pas de
onselectionend.
Et l’événement onselect n’est disponible que sur les éléments de
formulaire input et textarea.
Notez aussi que la sélection disparaît lors du clic de la souris. Pour
traiter la sélection lors du clic de la souris, il est nécessaire de
désactiver avec preventDefault() le comportement par défaut du clic
lors du mousedown :
btnAddSmiley.addEventListener("mousedown", preventDeselect);
function preventDeselect(evt) {
evt.preventDefault();
}

12.6.4 Le copier-coller

Le presse-papiers (clipboard en anglais) est la zone de mémoire


qui permet de passer des données entre différentes applications de
l’appareil de l’utilisateur. Le presse-papiers supporte trois actions :
Copie (copy) de la sélection dans le presse-papiers avec le
raccourci clavier [ctrl]+c ou par un clic droit pour afficher le
menu contextuel.
Coupage (cut) de la sélection, c’est-à-dire copie dans le presse-
papiers et suppression de la sélection avec [ctrl]+x ou par un
clic droit pour afficher le menu contextuel.
Collage (paste) du contenu du presse-papiers à l’emplacement
du curseur avec [ctr]+v ou par un clic droit pour afficher le
menu contextuel.

◆ Les événements sur le presse-papiers

Le document supporte donc aussi trois événements ClipboardEvent


de type oncopy, oncut, onpaste qui se déclenchent lors de la
manipulation du presse-papiers.
/* Détection de l'événement oncopy */
document.addEventListener("copy", function(evt) {
console.log("Détection de "+evt.constructor.name+" type="+evt.type);
});

Les événements ClipboardEvent possèdent la propriété


supplémentaire clip boardData de type DataTransfer, celui des
événements de drag and drop.

◆ Copier dans le presse-papiers avec JavaScript

La copie dans le presse-papiers de l’actuelle sélection se réalise en


JavaScript avec la méthode execCommand() de JavaScript :
Bool document.execCommand(String commande)

Cette méthode retourne true si l’instruction commande passée en


paramètre a été correctement exécutée.
Pour copier la sélection en cours, il suffit donc d’écrire :
document.execCommand("copy");

Les commandes cut et paste fonctionnent également. Il existe


d’autres commandes, mais qui ne sont pas encore compatibles avec
l’ensemble des navigateurs à ce jour.
Pour faciliter la copie d’information de l’écran et éviter tout risque
d’erreur lors de la sélection des données à copier (comme pour un
script ou une clé d’activation par exemple), nous allons créer un
bouton de copie dans le presse-papiers :
<div class="container">
<div class="inline" id="bitcoin">
1K1FAKFAYLnn7cpPJrwsM9tw1sNLS5oCFj
</div>
<div class="buttonCopy" data-copy="bitcoin">
<i class="fas fa-lg fa-copy"></i>
</div>
</div>

Avec quelques propriétés CSS, le rendu dans la page est :

Le clic sur le bouton déclenche une fonction qui repère dans data-
copy l’identifiant du div à copier dans le presse-papiers :
function btnCopy(evt) {
var btn=evt.currentTarget;
/* Recherche de l'identifiant du contenu à copier */
var id=btn.dataset.copy;
var elt=document.getElementById(id);
elt.parentNode.className="container";
console.log("ID de l'élément à copier = "+id);
/* Création de la sélection par programmation de Range */
var sel=window.getSelection();
var range=document.createRange();
range.selectNodeContents(elt);
console.log(range.toString());
sel.removeAllRanges();
sel.addRange(range);
/* Copy in the clipboard */
document.execCommand("copy");
/* Animation css flash sur l'élément container parent */
elt.parentNode.className="container flash";
/* Effacement de la sélection */
range.collapse();
}

Pour signaler à l’utilisateur que son adresse Bitcoin est bien dans le
presse-papiers, une animation CSS via @keyframes lance un flash
sur la zone div.container. La sélection est supprimée avec collapse().
Le script complet est disponible à l’adresse http://tjs.ovh/copy.
Le presse-papiers contient potentiellement des données
sensibles, issues d’autres applications, comme des mots de
passe ou des clés privées. L’accès en lecture au contenu du
presse-papiers est donc restreint par les navigateurs.

12.6.5 Le préchargement d’images

Une interactivité réussie implique une anticipation des besoins des


différentes ressources qui vont être utilisées dans la page. Pour
garantir une animation réussie et fluide, il faut souvent précharger
dans le cache du navigateur une série d’images qui seront affichées
plus tard, sur un mouvement de souris ou à l’affichage d’un bloc de
navigation. Le chargement d’images se fait par simple
programmation, en utilisant le constructeur Image :
/* Constructeur de Preload */
function Preload(images, callback=null) {
this.images=[];
this.nbLoaded=0;
this.callback=callback;
for (var i=0; i<images.length; i++) {
this.images[i]=new Image();
this.images[i].src=images[i];
var thisPreload=this; /* Sauvegarde du this dans le contexte du onload */
this.images[i].onload = function(evt) {
console.log("Image "+this.src+" loaded ");
thisPreload.nbLoaded++;
if (thisPreload.nbLoaded==thisPreload.images.length) {
/* Toutes les images chargées : lancement de l'éventuel callback */
if (typeof thisPreload.callback === "function") {
thisPreload.callback();
}
}
}
}
}
/* Lancement du préchargement */
var images=["media/image1.gif","media/image2.gif","media/image3.gif"];
var preload = new Preload(images, function() {
console.log("Préchargement terminé");
document.getElementById("end").innerHTML="Préchargement terminé";
});

Grâce à l’objet Preload construit qui détecte l’avancement du


chargement, il est possible de déclencher une fonction callback
quand toutes les images ont bien été chargées. La console contient
à l’exécution du script :
Image https://www.toutjavascript.com/livre/media/image1.gif
loaded
Image https://www.toutjavascript.com/livre/media/image2.gif
loaded
Image https://www.toutjavascript.com/livre/media/image3.gif
loaded
Préchargement terminé
Le script est visible à l’adresse http://tjs.ovh/preload.

Le mécanisme de cache du navigateur conserve en local une


copie des images qui seront utilisées en cas de besoin et
affichées immédiatement.
13

Les formulaires

Objectif
Dans ce chapitre, nous allons étudier les formulaires et leurs
différents éléments. Ils permettent aux utilisateurs de saisir
facilement des informations. Même si les interactions sur les
sites web se font de plus en plus de façon plus fluide et
ludique, les formulaires restent indispensables pour envoyer
des données vers le serveur.

_ 13.1 L’OBJET FORM


Un formulaire est défini par la balise <form>, qui permet d’accueillir
différents éléments de saisie et d’interactivité. Commençons par un
exemple très simple composé d’une zone de texte et d’un bouton :
<form id="form1" name="monForm" method="post" action="page.php">
Saisir du texte : <input type="text" name="saisie">
<input type="submit" value="Valider le formulaire">
</form>

Sans aucune mise en forme via CSS, chaque navigateur et chaque


système d’exploitation affichent le formulaire avec un style par
défaut :
Rendu du formulaire sous Safari macOS

Rendu du formulaire sous Edge Windows

L’accès à l’objet JavaScript form peut se faire de plusieurs façons :

var f1=document.getElementById("form1"); // Avec la recherche par id


var f2=document.forms[0]; forms // Avec l'indice 0 du tableau
// Avec l'indice nommé name sur
var f3=document.forms["monForm"];
forms
// Directement avec le name du
var f4=document.monForm;
form

La recherche classique par identifiant id fonctionne bien sûr.


Historiquement, avant l’apparition du DHTML, les formulaires étaient
accédés par leur nom dans l’attribut name, via le tableau forms. Ce
type d’accès fonctionne toujours aujourd’hui. La syntaxe raccourcie
document.nomFormulaire est d’ailleurs la plus lisible et la plus
compacte. Dans ce livre, nous utiliserons le plus souvent cette
notation par nom de formulaire.

Attention à ne pas confondre les attributs id et name lors des


accès aux formulaires.

Notre fonction getHeritages() nous montre qu’un formulaire est de


type HTMLForm Element et hérite bien de la chaîne habituelle des
éléments HTML :
["HTMLFormElement", "HTMLElement", "Element", "Node", "EventTarget" ,"Object"]

13.1.1 Les propriétés d’un objet Form


Les propriétés JavaScript d’un formulaire permettent d’accéder en
lecture et en écriture aux attributs de la balise :
Propriété Utilisation
Nom du formulaire permettant son
name
accès via JavaScript
Adresse de la page vers laquelle est
action envoyé le formulaire lors de
sa soumission
Méthode de soumission du formulaire :
method
get ou post
Destination de la page de soumission.
Par défaut, la fenêtre en cours. Avec la
target
valeur _blank, le formulaire soumis
ouvre une nouvelle fenêtre
Type d’encodage du contenu. Utilisé
enctype pour l’envoi de fichiers. Voir l’élément
input type=file

L’extension Web Developer propose un onglet complet dédié aux


formulaires qui permet d’afficher toutes les informations
directement sur la page, comme les champs cachés, le nom de
tous les champs, les mots de passe…

13.1.2 La soumission d’un formulaire

◆ Les modes de soumission

Le but d’un formulaire est d’envoyer les données saisies par


l’utilisateur vers le serveur du site pour les traiter. Tous les
traitements sont envisageables et tous s’appuient sur les
formulaires :
Envoi d’une question de prise de contact.
Recherche sur un moteur.
Demande de devis.
Commande en ligne.
Dépôt d’un commentaire.
Publication d’un post ou d’une photo.
Vote/partage/like d’une publication.
Participation à un jeu.
Un formulaire sans envoi de données est totalement inutile. Un
développeur web doit donc savoir comment envoyer les données
saisies, et idéalement comment les lire et les manipuler une fois
qu’elles sont parvenues sur la page action de son serveur.
La soumission d’un formulaire peut se faire de trois manières :
Clic sur un bouton input de type submit.
Appel de la méthode submit() du formulaire.
Frappe de la touche [entrée] dans une zone de texte.

◆ Les méthodes de soumission

La propriété method de la balise form définit le mode d’envoi des


données, et donc le mode de réception sur le serveur.
Avec un formulaire en method="get", les données sont envoyées
vers l’URL de la page définie par l’attribut action. En soumettant
notre formulaire d’exemple avec la chaîne bienvenue dans ce livre
dans la zone de texte, le serveur recevra les données sous cette
forme :
page.php?saisie=bienvenue+dans+ce+livre
Les données passées dans l’URL sont automatiquement encodées,
avec par exemple le signe + pour remplacer un espace. (Voir les
fonctions d’encodage de chaînes de caractères.)
Si l’attribut method n’est pas renseigné, le mode get s’applique par
défaut.
Avec un formulaire en method="post", les données sont envoyées
de manière invisible. L’URL de destination sera :
page.php
Dans le cas d’une navigation arrière par le bouton [Page précédente]
du navigateur vers une page chargée par un formulaire en mode
post, un message d’avertissement sera affiché :

Voici un tableau comparant les implications pour les deux méthodes


d’envoi de données de formulaire :

Les avantages et inconvénients des méthodes


get et post

Implication get post


Les données sont
Les données sont masquées pour
Visibilité visibles dans l’URL de garantir le
destination. maximum de
confidentialité.
L’URL de résultat peut
être partagée
Le partage d’URL
directement par un
ne transmet pas les
copier-coller de
Partage d’URL données envoyées
l’adresse. Les
et n’est donc pas
moteurs de recherche
possible.
utilisent donc la
méthode get.
Taille La taille maximale La limite de taille
de données est est paramétrée par
limitée à quelques le serveur, mais
milliers d’octets. peut dépasser
des dizaines de Mo.
La navigation
arrière implique un
message
du navigateur qui
La navigation arrière
Navigation arrière demande une
n’est pas interrompue.
confirmation pour
soumettre le
formulaire une
nouvelle fois.

◆ La détection de la soumission

La soumission d’un formulaire (submit en anglais) est détectée par


l’événement nommé onsubmit. Comme pour tout événement
JavaScript sur une balise, il est possible de le déclarer par le
gestionnaire d’événements ou par l’attribut onsubmit de <form>.
Cette détection est très utile pour contrôler la saisie avant l’envoi du
formulaire.
En retournant false depuis l’événement, la soumission s’interrompt.
Reprenons notre exemple basique en ajoutant l’attribut onsubmit, qui
retourne la fonction checkForm(). La fonction reçoit en paramètre
this, représentant le formulaire en cours de manipulation :
<form name="monForm" action="page.php" onsubmit="return checkForm(this)">

La fonction est définie avec :


function checkForm(f) {
if (f.saisie.value=="") {
alert("Il faut saisir votre texte");
f.saisie.focus();
return false;
}
}

Le paramètre f est le formulaire en cours de contrôle. Si la valeur de


l’élément saisie est vide, la fonction retourne false, empêchant la
soumission de se poursuivre.
Avec un gestionnaire d’événements à la place de l’attribut onsubmit,
nous pouvons écrire :
document.monForm2.addEventListener("submit", function(evt) {
if (evt.currentTarget.saisie.value=="") {
alert("Il faut saisir votre texte");
document.monForm2.saisie.focus();
evt.preventDefault();
}
});

Ici, pour empêcher le formulaire d’être soumis si une saisie n’est pas
correcte, nous utilisons la méthode preventDefault().

_ 13.2 LES ÉLÉMENTS DE FORMULAIRES


Entrons maintenant dans l’étude détaillée des différents éléments de
saisie que le HTML propose à l’intérieur d’une balise form. L’accès
JavaScript aux éléments peut se faire de différentes manières. Le
tableau elements d’un formulaire contient tous les éléments indicés,
par ordre de création dans le code HTML ou par leur attribut name :
document.forms["monForm"].elements["saisie"]
document.forms[0].elements[0]

Un accès direct par la propriété name est possible également :


document.monForm.saisie

13.2.1 Les propriétés des éléments


◆ Les propriétés

Les éléments de formulaire héritent de la chaîne HTMLElement et


possèdent des propriétés typiques liées au formulaire :
value est la chaîne de caractères représentant la valeur de
l’élément.
form est une référence vers le formulaire contenant l’élément.
D’autres propriétés propres à chaque type d’éléments sont
détaillées dans ce chapitre.

◆ Les événements

L’événement onfocus est déclenché quand le curseur clavier entre


dans un élément.
L’événement onblur se déclenche quand l’élément perd le focus.
L’événement onclick détecte le clic sur un élément, le plus souvent
un bouton, et déclenche un traitement.
L’événement onselect détecte la sélection du contenu d’un élément
de formulaire.
L’événement onchange détecte le changement de contenu d’un
champ par l’utilisateur. Cet événement n’est pas déclenché à chaque
changement de contenu mais quand le navigateur considère que la
saisie du champ est terminée.
Entrez votre texte : <input type="text" name="champ" id="champ">
<input type="button" onclick="this.form.champ.value=''" value="Vider">
<script type="text/javascript">
var champ=document.getElementById("champ");
champ.addEventListener("blur", function(evt) {
console.log("Perte du focus");
});
champ.addEventListener("change", function(evt) {
console.log("Modification du contenu");
});
</script>
La page affiche :

Lors de la saisie de texte par l’utilisateur, l’événement onchange


n’est pas déclenché. Il faut que le champ perde le focus pour que
onchange s’active, avant l’événement onblur :
Modification du contenu
Perte du focus
Le changement du contenu par programmation ne déclenche pas
l’événement ! Testez le fonctionnement sur le script à l’adresse
http://tjs.ovh/onchange.
Les événements onkeyup, onkeypress et onkeydown permettent de
gérer la saisie au clavier à chaque touche enfoncée, pour interdire
par exemple des caractères non autorisés ou formater la saisie en
temps réel.

◆ Les méthodes

Chaque événement peut être déclenché par sa méthode associée. Il


est donc possible de forcer le focus sur un élément en utilisant la
méthode focus() :
document.monForm.saisie.focus();

De la même manière, pour sélectionner le contenu d’un élément, on


utilise la méthode select() :
document.monForm.saisie.select();

Reportez-vous au chapitre de la gestion des événements pour le


traitement des sélections et du copier-coller.

13.2.2 Les champs de saisie texte


La majorité des éléments de saisie sont créés à partir de la balise
HTML input.
L’attribut type définit le comportement et l’apparence de l’élément.
Par défaut, le champ input est de type="text". Il s’agit de l’élément
basique de saisie dans un formulaire. Le type peut être affiné selon
la structure de saisie attendue. Voici une liste de types possibles
pour les champs de saisie texte :
Type Utilisation
text Zone de saisie de texte brut
email Zone de saisie d’une adresse mail
date Zone de saisie assistée d’une date
number Zone de saisie d’un nombre décimal
Zone de recherche, avec souvent un
search bouton pour effacer d’un clic la saisie
existante
tel Numéro de téléphone
url Adresse d’une page web

Le choix du type pertinent par rapport à la saisie attendue est


particulièrement utile sur les smartphones. Le navigateur affiche
directement le clavier adapté, avec les chiffres pour un numéro de
téléphone ou la touche @ directement accessible pour un type
email.

Les éléments input possèdent des attributs de mise en forme, qui


sont également accessibles en JavaScript :
Attribut Usage
Type de l’élément (voir tableau
type précédent pour la liste des types
disponibles)
name Nom de l’élément pour l’accès
JavaScript
Contenu du champ sous forme de
value
chaîne de caractères
Identifiant de l’élément pour l’accès
id
par getElementById()
Indicateur on ou off qui autorise
l’affichage automatique, en fonction
autocomplete
de l’historique de saisie, de
propositions de fin de saisie
Booléen qui indique si le champ est
grisé. Un champ grisé n’est plus
disabled accessible par l’interface et n’est pas
envoyé au serveur à la soumission du
formulaire
Longueur du champ à l’affichage.
size Préférez l’usage de CSS pour définir
une mise en forme
Longueurs minimale et maximale de
minlength et maxlength
texte acceptées dans le champ
Valeurs minimale et maximale pour un
min et max
champ de type number
Texte affiché par défaut avant toute
placeholder saisie. Permet de donner des
indications à l’utilisateur
Booléen qui indique si le navigateur
spellcheck doit vérifier l’orthographe de la saisie
de l’utilisateur
Expression régulière de contrôle de la
pattern
saisie
required Booléen qui indique si le champ est
obligatoirement renseigné avant la
soumission
En complétant notre champ de saisie avec ces attributs, nous
pouvons personnaliser finement son apparence et son
fonctionnement.
Pour éviter l’affichage d’une liste de propositions de saisies :

Pour éviter le contrôle d’orthographe sur un champ pseudo, qui a


toutes les chances de ne pas être dans le dictionnaire :

Voici comment nous pourrions définir notre balise pour :


empêcher la saisie automatique,
ne pas effectuer le contrôle d’orthographe,
préciser que la valeur attendue doit mesurer entre 6 et 20
caractères :
<input type="text" name="saisie" autocomplete="off" spellcheck="false"
placeholder="Choisissez un pseudo" minlength="6" maxlength="20">

Grâce à cette déclaration un peu plus complète, le navigateur


contrôle la saisie et indique clairement comment corriger l’erreur
détectée.

Notez que chaque navigateur propose son propre style d’affichage


et ses propres messages, qu’il n’est pas possible de paramétrer :
Tous ces attributs sont très utiles pour améliorer l’ergonomie d’un
formulaire, faciliter la compréhension des éléments attendus et
optimiser les chances que les utilisateurs finalisent correctement
leur saisie. Il s’agit d’optimiser à moindres frais le taux de
transformation, c’est-à-dire le rapport entre le nombre de
formulaires correctement soumis et le nombre de visiteurs sur le
formulaire.

◆ L’accès JavaScript

L’accès à la valeur du champ se fait par la propriété value. Pour


tester si la saisie fait plus de 6 caractères, il faut donc utiliser :
if (document.monForm.saisie.value.length>6) { }

Il ne faut pas confondre l’objet JavaScript de type HTMLElement et


sa valeur value. Le JavaScript étant très laxiste, l’expression
suivante ne générera pas d’erreur mais ne contrôlera pas non plus la
longueur de la saisie :
document.monForm.saisie.length>5

◆ Le contrôle de saisie par le navigateur

Nous avons déjà vu plus haut le contrôle de saisie par le navigateur


sur les propriétés maxlength et minlength. L’attribut pattern ajoute,
grâce aux expressions régulières (voir ici), des contrôles plus
poussés sur la structure de la saisie :
<input type="text" name="saisie" pattern="[0-9a-zA-Z]{6,20}">

Ici pattern définit un format alphanumérique de 6 à 20 caractères. Si


la saisie n’est pas conforme, une tentative de soumission du
formulaire affiche ce genre de message :
Les contrôles natifs sont réellement pratiques car ils sont très
rapides à mettre en œuvre et ne nécessitent aucune programmation
additionnelle. Ils présentent malgré tout quelques inconvénients :
Chaque navigateur gère les contrôles et les messages d’erreur
à sa façon, avec un résultat imprévisible.
Les messages d’information ne sont pas personnalisables.
Des contrôles de saisies avancés ne sont pas réalisables, par
exemple sur la robustesse d’un mot de passe.
Nous verrons au paragraphe dédié comment réaliser des contrôles
complets.

Attention, le contrôle de saisie du côté client, que ce soit par les


procédures internes du navigateur ou par un script personnalisé,
n’est utile que pour les utilisateurs classiques d’une application.
Ces contrôles servent à guider la saisie et à optimiser le taux de
transformation et la fiabilité des données.
Ces contrôles client n’empêchent en aucun cas d’envoyer au
serveur des données qui ne correspondent pas à ce qui est
attendu. Il existe des logiciels et des navigateurs spécialisés dans
l’envoi de données dangereuses vers le serveur, qui détectent les
failles de sécurité et les vulnérabilités, pénètrent le serveur pour y
dérober des données ou y installer des scripts malveillants.

◆ Les champs cachés

Un élément input de type hidden n’apparaît pas à l’écran, mais il


reste manipulable par JavaScript et son contenu sera correctement
envoyé vers le serveur lors de la soumission du formulaire. Les
champs cachés sont utiles pour transmettre des données techniques
en plus des données saisies par l’utilisateur. On peut imaginer par
exemple transmettre dans le champ resolution la résolution de
l’écran de l’appareil :
<input type="hidden" id="resolution" name="resolution" />
Il suffit d’accéder à la propriété value du champ :
var reso=document.getElementById("resolution");
reso.value=window.screen.width+"X"+window.screen.height;

Les champs cachés permettent aussi de lutter contre les bots


malveillants, qui essayent de soumettre des formulaires frauduleux
(votes, likes, participations à des concours) ou des spams
(commentaires, forums, contacts).
Le principe est de trouver une donnée qui sera évidente, voire
automatique, dans le cas d’une navigation naturelle d’un humain
mais qu’un robot remplira au hasard. Une fois le champ envoyé au
serveur, il reste à vérifier la cohérence du contenu transmis. Ce type
de test est appelé Captcha et vise à s’assurer que le répondant
n’est pas un ordinateur mais un utilisateur humain légitime.
La progression incessante de l’intelligence des robots rend le
combat fastidieux et sans fin. Google propose donc l’outil gratuit
reCAPTCHA, un simple JavaScript à copier-coller dans votre
formulaire. L’outil analyse le risque d’avoir affaire à un robot et
propose différents problèmes à résoudre pour garantir que
l’utilisateur est bien un humain.
Si l’outil est gratuit, Google le rentabilise grâce aux réponses aux
quiz, qui permettent par exemple de valider des numéros de rue sur
Google Street View ou de vérifier des résultats d’intelligence
artificielle en reconnaissance visuelle.

◆ Les champs typés

Les navigateurs ont mis en place des interfaces plus intuitives pour
la saisie de données structurées. Par exemple, pour la saisie de
date, il suffit de définir l’élément input avec le type date :
<input type="date" name="birthday" />

Ce qui donnera dans Google Chrome :


De la même manière, un élément input de type number n’autorisera
que la saisie de nombres, avec l’apparition d’un curseur pour faciliter
la saisie sans utiliser de clavier :
<input type="number" name="nbEnfant" />

Avec ce type de rendu :

Le champ de type search est également intéressant car il ajoute un


bouton en forme de croix qui supprime d’un clic le texte existant :
<input type="search" name="motcle" />

Le bouton de suppression du texte est très utile sur un smartphone


tactile pour effacer d’un clic un champ complet :

Les interfaces de saisie sont définies par le navigateur et ne


peuvent pas être modifiées. Pour obtenir une interface
personnalisée, il est nécessaire de coder la fonctionnalité en
JavaScript.

13.2.3 Les mots de passe

Les mots de passe sont encore au cœur de l’informatique. Même si


de nombreux projets tentent de leur trouver un successeur plus
sécurisé et plus pratique, il faut bien avouer qu’aucun n’y est
parvenu. En attendant, les internautes multiplient les accès aux sites
Internet avec des mots de passe.
Un champ de mot de passe est un HTMLInputElement de type
password. Grâce à ce type, les caractères tapés n’apparaissent pas
lisibles à l’écran mais sous forme de bulles :

Comme le type est accessible en JavaScript, il est possible de


modifier à la volée le type du champ de mot de passe pour le forcer
en type text et rendre ainsi la saisie visible à l’utilisateur :
document.getElementById("viewpass").addEventListener("mouseenter",
function(evt) {
evt.target.previousSibling.setAttribute("type", "text");
});
document.getElementById("viewpass").addEventListener("mouseleave",
function(evt) {
evt.target.previousSibling.setAttribute("type", "password");
});

Ici, en passant la souris sur l’icône #viewpass, le type password est


forcé à text :

L’association d’un champ de type password et d’un bouton submit


active le gestionnaire de mots de passe du navigateur, qui propose
ainsi d’enregistrer le couple identifiant/mot de passe. Chaque
navigateur propose une interface différente. Il existe aussi des
logiciels externes qui prennent en charge l’ensemble des accès via
un mot de passe maître. Dans tous les cas, il est indispensable que
votre page de connexion propose à l’utilisateur d’enregistrer ses
accès.

◆ La sécurisation des mots de passe

La robustesse des mots de passe est un enjeu important, à l’heure


où les piratages se multiplient. De nombreux sites obligent à créer
des mots de passe, complexes et donc plus robustes, contenant
obligatoirement un mélange de minuscules, majuscules, chiffres et
caractères spéciaux. Si l’intention est louable, la réalisation est
souvent médiocre, entraînant un enchaînement de messages
d’erreurs peu explicites, avec un risque que l’utilisateur abandonne
son inscription !
Pour les services en ligne sensibles (bourse et banque en ligne, outil
administratif, réseaux sociaux d’entreprises…), l’accès par un seul
mot de passe est jugé trop peu sécurisé. Une protection
supplémentaire est ajoutée par une « Authentification à 2
Facteurs » (2FA en abréviation anglaise). En plus du mot de passe
classique, un second facteur de sécurisation est ajouté. Il s’agit le
plus souvent d’un code pin avec une durée de vie réduite reçu par
SMS, par une application mobile supplémentaire (comme Google
Authenticator) ou par une carte à puce externe.
Grâce au principe du double facteur, même si le mot de passe
principal est dérobé, l’accès n’est pas encore possible. La plupart du
temps, l’utilisateur légitime recevra aussi une notification l’informant
d’une tentative d’accès, lui permettant d’agir et de modifier le mot de
passe corrompu.

◆ L’impact du protocole sécurisé

L’usage du protocole https:// sur des pages avec des mots de passe
est fortement recommandé. Le navigateur affiche dans la barre
d’URL un message inquiétant pour un utilisateur non informé :

La console explique l’apparition de l’avertissement :


This page includes a password or credit card input in a non-
secure context. A warning has been added to the URL bar.
Chaque nouvelle version de navigateur augmente la visibilité du
message pour forcer les propriétaires de sites à utiliser un protocole
sécurisé.
En plus de l’affichage de l’avertissement, un impact négatif sur le
référencement est officiellement mis en place pour les sites qui
conservent le protocole http://, même sur des pages qui ne
manipulent aucune donnée sensible.

13.2.4 Les boutons

Les boutons sont aussi des éléments input. Il existe trois types de
boutons associés à un formulaire :
Le bouton d’action de type button, associé à un événement
onclick :
<input type="button" value="Cliquez le bouton" />
Le bouton de soumission de type submit, qui déclenche la
soumission du formulaire :
<input type="submit" value="Soumettre" />
Le bouton de type image est peu employé. Ce bouton se
comporte comme un bouton submit mais est remplacé par une
image :
<input type="image" src="imagebouton.jpg" />
Par défaut, les boutons, ainsi que tous les autres éléments du
formulaire, sont mis en forme par le style natif du navigateur et du
système d’exploitation. Avec quelques propriétés CSS, il est
possible d’obtenir un rendu équivalent sur tous les navigateurs.

◆ La balise button

Le HTML5 propose la balise <button>, qui peut remplacer les


éléments input classiques des formulaires. Par défaut, l’apparence
des éléments button est la même que les input de type bouton.
<button name="bouton">Cliquez sur ce bouton</button>

Le button est relié au formulaire auquel il appartient en tant que


balise fille. Mais l’attribut form peut contenir l’identifiant d’un autre
formulaire et permet ainsi de placer un bouton en dehors du groupe
de nœuds fils du formulaire.
13.2.5 Les cases à cocher

Les balises input peuvent aussi prendre l’apparence de cases à


cocher. En informatique, il faut distinguer deux types de cases à
cocher :
La checkbox est une case qui peut être cochée (activée) ou
décochée (inactive). Une checkbox est donc le bon élément
pour afficher un choix binaire. Dans toutes les applications
informatiques, les cases à cocher sont carrées et se
remplissent d’une croix (check) lorsqu’elles sont activées.
Le radio button (bouton radio) est une case d’un groupe
d’options. Dans ce groupe, il n’est possible de cocher qu’une
seule des cases. Le fait de cocher une autre case décoche la
case précédemment choisie.

Grâce à leur apparence universelle, la structure des questions


sous forme de cases à cocher est immédiatement comprise. Ne
tentez pas de changer les habitudes des utilisateurs par des
mises en forme CSS contre-intuitives.

◆ Les checkbox

Une checkbox est idéale pour poser une question fermée, qui attend
comme réponse Oui ou Non :
<input type="checkbox" name="reglement" value="1" />
J'accepte le règlement du site Internet

L’attribut checked est un booléen accessible en lecture et en


écriture. Si checked vaut true, la case est cochée. Lors de la
soumission, le formulaire enverra la valeur value pour cet élément.
Si la case n’est pas cochée, le formulaire n’enverra aucune valeur
pour l’élément.
Pour contrôler lors de la soumission que la case est bien cochée :
if (!document.monForm.reglement.checked) {
alert("Vous devez accepter le règlement");
return false;
}

L’attribut HTML checked permet de précocher la case à l’affichage


du formulaire :
<input type="checkbox" name="reglement" value="1" checked />

Dans ce cas, la case est précochée au chargement du formulaire :

Attention à la législation, y compris pour de simples cases à


cocher. Le RGPD (Règlement général sur la protection des
données) est très strict sur la manière d’obtenir le consentement
des utilisateurs. Les cases d’acceptation précochées ne sont pas
tolérées.

◆ Les boutons radios

Les boutons radios s’utilisent quand l’utilisateur doit choisir une


proposition parmi un ensemble de possibilités. Par exemple, pour lui
demander son animal préféré :
Vous préférez les :<ul>
<li><input type="radio" name="animal" value="chien" /> Chiens</li>
<li><input type="radio" name="animal" value="chat" /> Chats</li>
<li><input type="radio" name="animal" value="cheval" /> Chevaux</li>
<li><input type="radio" name="animal" value="reptile" /> Reptiles</li>
</ul>

À l’écran, la liste des possibilités apparaît :


Le regroupement des possibilités est réalisé grâce à la propriété
name, qui est la même pour toutes les cases. Grâce à ce nommage,
une seule case peut être cochée à la fois.
Lors de la soumission du formulaire, le serveur recevra la valeur
value de la case cochée.
Au chargement de la page, aucune case n’est cochée. Pour vérifier
qu’un choix a bien été fait, il est nécessaire de réaliser une boucle
sur les éléments de même name :
var animal=""; /* Avant la boucle, animal est vide */
for (var i=0; i<document.monForm.animal.length; i++) {
if (document.monForm.animal[i].checked) {
animal=document.monForm.animal[i].value;
break; /* Case cochée trouvée, on peut sortir de la boucle */
}
}
if (animal=="") {
alert("Veuillez saisir votre animal préféré");
} else {
console.log(animal);
}

Si une case est bien checked, la variable animal prend sa value et


on sort de la boucle. À la fin de la boucle, si la variable animal est
vide, un message est affiché, sinon le choix est affiché dans la
console.

◆ Utilisez les labels

Les cases à cocher sont très petites et donc difficiles à cliquer. Pour
faciliter l’interaction, le bon réflexe est de lier le libellé avec la case
concernée. Ainsi, un clic sur le libellé cochera ou décochera sa case.
La liaison se réalise entièrement en HTML avec la balise <label> qui
reçoit dans son attribut for l’identifiant de la case associée.
Pour associer le texte d’acceptation à la case à cocher cgv, il suffit
d’écrire :
<input type="checkbox" name="cgv" id="cgv" value="1" />
<label for="cgv">J'accepte les conditions de vente</label>

13.2.6 Les fichiers

◆ Le champ input type=file

Le dernier usage de la balise input est dédié au sujet complexe de la


sélection et de l’envoi de fichiers vers le serveur, appelé upload.

Il faut bien distinguer l’upload, qui est un envoi montant de


l’utilisateur vers le serveur, du download (ou téléchargement), qui
consiste à récupérer en local un fichier hébergé sur le serveur.

Quand on affecte le type file à un champ input :


<input type="file" name="monFichier" />

le champ de formulaire prend l’apparence suivante :

Quand l’utilisateur clique sur le bouton « Choisir un fichier », une


boîte de dialogue d’exploration de fichiers s’ouvre pour permettre le
choix d’un fichier. Une fois le fichier choisi, le champ affiche le nom
du fichier sélectionné :

Le champ file accepte les attributs :


accept reçoit le type de fichiers autorisés. Par exemple
accept=".png", accept="image/*" ou accept="image/png,
image/gif, image/jpeg".
multiple permet la sélection de plusieurs fichiers simultanément.
required force le navigateur à contrôler la présence d’un fichier
lors de la soumission.
◆ Le cas des smartphones

Notez qu’une fois de plus chaque navigateur et chaque système


d’exploitation proposent un rendu différent, qu’il n’est pas possible
de modifier. Le cas des smartphones est particulier car ils ne
disposent pas réellement de gestionnaire de fichiers. Sur un iPhone,
l’activation d’un champ file déclenche ce menu, qui permet de
parcourir les photos existantes ou de lancer l’appareil photo :

◆ L’envoi vers le serveur

L’envoi de fichier vers le serveur impose de configurer le formulaire :


method="post" : l’envoi de fichier en get ne fonctionne pas.
enctype="multipart/form-data" : enctype définit l’encodage du
formulaire pour lui indiquer qu’il reçoit ici des données de
fichiers.

◆ Sécurisation des champs file

L’accès au disque dur de l’utilisateur par ce champ file ouvre de


potentielles fuites de données. Heureusement, les navigateurs ont
sécurisé les champs file et interdisent toute modification de leur
contenu par programmation. Seul l’utilisateur peut parcourir le
disque dur et choisir un fichier.
L’upload vers le serveur est également une source de risques très
importants, voire catastrophiques. Un utilisateur malveillant peut
tenter d’envoyer un fichier exécutable sur le serveur, qui lui donnerait
ainsi accès à l’ensemble de votre site. Il est indispensable de
contrôler les saisies des utilisateurs et donc la fiabilité des fichiers
transmis. Rappelez-vous que les contrôles du côté navigateur ne
sécurisent absolument pas votre application. Ils ne servent qu’à
rendre l’application plus ergonomique.

◆ L’accès en JavaScript

Les fichiers sélectionnés dans le champ input sont accessibles par la


propriété files de type FileList et contenant des objets File. Chaque
objet File possède les propriétés :
name, qui correspond au nom du fichier sur le poste client.
size, qui correspond à la taille en octets du fichier.
type, qui est une chaîne de caractères contenant le type MIME
du fichier.
lastModified, qui est la date de modification du fichier exprimée
en nombre de secondes depuis le 1er janvier 1970.

Le type MIME est une chaîne de caractères standardisée


définissant le type et le format d’un fichier. Il existe des centaines
de types reconnus comme image/png, application/json,
audio/aac…

Avec les propriétés issues du champ file, il est possible d’afficher


des informations sur les fichiers sélectionnés, dès le déclenchement
de l’événement onchange :
document.getElementById("file").addEventListener("change", function(evt){
var p=document.getElementById("preview"); /* Bloc d'affichage fichiers */
p.innerHTML=""; /* Effacer le contenu initial de #preview */
for (var i=0; i<this.files.length; i++) {
var f=this.files[i];
var div=document.createElement("div");
div.className="fichier";
var span=document.createElement("span");
span.innerHTML=f.name+" ("+getHumanSize(f.size)+")";
var vignette=document.createElement("img");
vignette.src = window.URL.createObjectURL(f);
/* Attacher les éléments HTML au DOM */
div.appendChild(vignette);
div.appendChild(span);
p.appendChild(div);
}
p.style.display="block";
});

Nous réutilisons notre fonction getHumanSize() créée du chapitre


des mathématiques.
La création des éléments et leur ajout dans le div#preview est
classique avec create Element() et appendChild(). La particularité
vient ici de la génération de la vignette d’une image disponible en
local. À cause des restrictions liées à la sécurité, nous devons
utiliser la méthode createObjectURL(), qui attend en paramètre
l’objet File complet.
À l’écran, nous obtenons, avec quelques propriétés CSS :

Retrouvez cet exemple à l’adresse http://tjs.ovh/upload.

◆ Utilisation du drag and drop

Pour faciliter l’upload de fichiers, les interfaces ont été simplifiées,


grâce au drag and drop. Nous avons déjà abordé les événements
liés aux interactions de glisser-déposer dans le chapitre de la
programmation événementielle.
Nous allons optimiser notre script précédent avec l’ajout d’une zone
de drag and drop, à la place de l’inélégant input file, permettant la
dépose directe de fichiers :
<div id="depose">Déposez vos images ou cliquez pour les choisir</div>
<input type="file" name="monFichier" id="file" accept="image/*" required multiple
style="display:none"/>

Ce qui donne à l’écran, avec quelques CSS supplémentaires :

Le champ file est caché et il est relié avec l’événement onclick sur le
div#depose :
var depose=document.getElementById("depose");
depose.addEventListener("click", function(evt) {
evt.preventDefault();
document.getElementById("file").click();
});

Les événements du drag and drop :


depose.addEventListener("dragover", function(evt) {
evt.preventDefault(); /* Pour autoriser le drop par JS */
});
depose.addEventListener("dragenter", function(evt) {
this.className="onDropZone"; /* Passe en surbrillance */
});
depose.addEventListener("dragleave", function(evt) {
this.className=""; /* La surbrillance s'efface */
});
depose.addEventListener("drop", function(evt) {
evt.preventDefault();
/* Tranfert de la liste des fichiers du drag and drop dans input file */
document.getElementById("file").files=evt.dataTransfer.files;
this.className=""; /* Surbrillance supprimée */
});

Le drag est détecté et matérialisé par une bordure large :


Pour transférer la liste des fichiers déposés, il suffit d’affecter dans la
propriété files de l’élément file la propriété files du dataTransfer.
Retrouvez cet exemple optimisé à l’adresse http://tjs.ovh/upload2.

◆ Envoi de fichiers en mode asynchrone

L’envoi de fichiers en mode asynchrone, c’est-à-dire pendant que


l’utilisateur réalise d’autres interactions, comme la rédaction d’un
email, est devenu la norme. Nous verrons dans le chapitre consacré
à AJAX, un exemple d’envoi d’image vers le serveur en mode
asynchrone.

13.2.7 Les listes déroulantes

◆ Les listes à choix unique

Les listes déroulantes sont largement utilisées sur les formulaires.


Elles ont l’avantage d’occuper peu de place à l’écran et ne se
déroulent qu’au moment où elles sont cliquées. Une fois le choix
réalisé, seule la valeur retenue apparaît à l’écran.
Une liste est définie par une balise <select>. Chaque ligne de la liste
est définie par une balise <option>. Pour demander le genre de film
préféré à un utilisateur, nous pouvons écrire :
<select name="film">
<option value="">Indiquez votre préférence</option>
<option value="comedie">Comédie</option>
<option value="aventure">Aventure</option>
<option value="romantique">Romantique</option>
<option value="historique">Historique</option>
</select>

Comme aucune option n’est présélectionnée, la première ligne est


retenue au chargement du formulaire :
Pour forcer une ligne, il faut ajouter l’attribut selected dans la balise
option :
<option value="aventure" selected="true">Aventure</option>

En JavaScript, un élément de type select contient les propriétés


dédiées :
selectedIndex : l’index de la ligne sélectionnée, qui commence
bien sûr à 0.
options : le tableau des balises option de la liste.
Un objet JavaScript de type option possède les propriétés :
value, qui correspond à l’attribut value de la balise.
text, qui correspond au texte affiché à l’écran.
Ainsi, pour afficher dans la console la valeur et le texte sélectionnés
par l’utilisateur, il suffit d’écrire :
var film=document.monForm.film;
console.log("Ligne sélectionnée : "+film.selectedIndex);
var option=film.options[film.selectedIndex];
console.log("Choix : "+option.text+" ("+option.value+")");

La console affichera ce genre de résultat :


Ligne sélectionnée : 2
Choix : Aventure (aventure)

◆ Les listes à choix multiples

En ajoutant l’attribut size à la balise select, nous pouvons agrandir la


taille de la liste et le nombre de lignes affichées à l’écran.
Si on ajoute l’attribut multiple à la balise, le navigateur autorise la
saisie de plusieurs lignes simultanément.
Créons une liste de choix de vacances sur six lignes autorisant la
saisie multiple :
<select name="vacances" size="6" multiple="true" id="vacances">
<option value="farniente">En mode farniente</option>
<option value="bronzage">Bien bronzé</option>
<option value="sport">En étant sportif</option>
<option value="festif">Fiesta obligatoire</option>
<option value="gastronomique">Avec de la gastronomie</option>
<option value="culturel">Avec de la culture</option>
</select>

À l’écran, une liste, sans aucune sélection initiale, apparaît :

Pour sélectionner plusieurs lignes, il faut maintenir la touche [cmd]


ou [ctrl] enfoncée.
Pour compter le nombre de lignes sélectionnées, une boucle est
également nécessaire :
var nb=0;
for (var i=0; i<document.monForm.vacances.options.length; i++) {
if (document.monForm.vacances.options[i].selected) {
nb++;
}
}

Notez que l’usage de liste à multi-sélection est moins courant car


elle implique de laisser une touche spéciale du clavier appuyée
pendant le clic. Un utilisateur débutant sera vite perdu et ne
parviendra pas à sélectionner plusieurs lignes en même temps.

13.2.8 Les zones de saisie sur plusieurs lignes

Il existe enfin une dernière typologie d’éléments de formulaire avec


la balise textarea :
<textarea name="texte">Contenu initial
sur deux lignes</textarea>

Avec un peu de mise en forme CSS, la zone apparaît ainsi :

Un textarea contient du texte brut, sans mise en forme. Il est utilisé


pour la saisie de texte long, comme le corps d’un message ou d’un
commentaire de forum.
Notez le coin inférieur droit, qui sert de poignée à l’utilisateur pour
redimensionner la zone à la volée. Pour interdire ce
redimensionnement, il faut ajouter les propriétés CSS suivantes :
overflow:auto;
resize:none;

Comme pour un champ texte input, le contenu de la zone textarea


est accessible par sa propriété value.

◆ Le format Markdown

Markdown est un langage de balisage léger permettant de mettre


en forme un document à partir d’une syntaxe texte simple à lire et à
écrire. Voici quelques exemples de mises en forme possibles :
Gras avec **texte en gras**.
Titres avec #Titre de niveau ou ###Titre de niveau 3.
Listes à puces avec :
* Item 1
* Item 2
Code source avec une indentation de quatre espaces ou une
tabulation :
if () {
/* Traitements */
}
Code source dans du texte avec `console.log(objet)`.
Lien hypertexte avec [libellé lien](http://addresse.lien).
Image avec ![Texte alt](http://addresse.image).

Notez que l’appellation de « langage » pour parler de simples


formats comme HTML ou Markdown est largement exagérée. Ces
formats ne font appel à aucune notion de programmation ni
d’algorithme.

Une zone textarea est donc idéale pour faire saisir du texte formaté
en Markdown à un utilisateur. Le texte saisi est ensuite envoyé au
serveur à la soumission du formulaire, sauvegardé dans une base
de données et affiché en HTML grâce à une bibliothèque de
conversion Markdown vers HTML.

13.2.9 Les éléments disabled

Tous les éléments de formulaires disposent d’un statut disabled


(« inactif » en français). Leur apparence est grisée, indiquant que
l’élément n’est pas modifiable par l’utilisateur.
Imaginons une liste de cases à cocher qui indique les différents
choix pour une prise de contact :
<ul>Indiquez quand vous préférez être contacté :
<li><input type="radio" name="periode" value="1" id="p1"> <label
for="p1">Matin</label></li>
<li><input type="radio" name="periode" value="2" id="p2"> <label for="p2">Après-
midi</label></li>
<li><input type="radio" name="periode" value="3" id="p3"> <label for="p3">A partir
de 19h</label></li>
<li><input type="radio" name="periode" value="4" id="p4"> <label
for="p4">Autre</label> <input type="text" name="autre" disabled="true"/></li>
</ul>

À l’écran, la liste apparaît, avec en dernière possibilité, un champ


texte grisé :
Ajoutons un événement sur les cases à cocher p1 à p4 pour griser
ou dégriser la zone de texte libre :
for (var i=1; i<=4; i++) {
document.getElementById("p"+i).addEventListener("change", function(evt) {
console.log(this.value+" "+this.checked);
if ((this.value=="4")&&(this.checked)) { /* Si la case p4 est cochée */
this.form.autre.disabled=false;
} else {
this.form.autre.disabled=true;
}
});
}

Quand on coche la case p4, la zone de texte se dégrise et prend le


focus.

Un champ grisé n’est pas envoyé au serveur lors de la soumission


du formulaire.

_ 13. 3 LES CONTRÔLES DE SAISIE


Le contrôle de saisie côté navigateur sert uniquement à guider
l’utilisateur dans les différentes étapes du formulaire. Les
navigateurs proposent nativement des contrôles de présence et de
format de données, grâce aux expressions régulières. Malgré tout,
certains tests ne sont pas réalisables, comme le contrôle des cases
à cocher. Nous allons, dans ce paragraphe, réaliser quelques
contrôles de saisie en JavaScript, sans utiliser les contrôles natifs.
Nous verrons dans le chapitre AJAX comment ajouter des contrôles
en temps réel à partir de données issues du serveur.

_ 13.4 LES DONNÉES CÔTÉ SERVEUR


Un formulaire qui n’envoie pas de données est totalement inutile.
Même si les traitements côté serveur dépassent largement l’objet de
ce livre, quelques notions de programmation serveur s’imposent au
développeur front-end.
Prenons comme exemple ce formulaire avec les différents types
d’éléments :

Parmi les particularités à noter :


Un champ caché type="hidden" contient la résolution d’écran.
Le champ naissance est un input type="date".
Les cases à cocher concernant les animaux sont déclarées
avec name="animal[]".
Le champ texte devant le bouton radio « Autre » est grisé avec
l’attribut disabled.
Retrouvez le formulaire complet et la déclaration de tous les
éléments à l’adresse http://tjs.ovh/post.

13.4.1 Le transport de données via HTTP

À la soumission du formulaire, la page post.php est appelée. Dans


la console du navigateur, sur l’onglet « Network », nous pouvons
observer les paramètres d’appel de cette page.
Le paragraphe « Form Data » contient toutes les données de
formulaires envoyées à la soumission.
Nous pouvons noter que :
Le champ caché resolution est bien transmis.
Le champ grisé n’est pas envoyé.
Le champ naissance est au format ISO (YYYY-MM-DD), qui ne
correspond pas à l’affichage mais qui assure que le contenu
sera toujours le même, quelles que soient la configuration ou la
langue de l’utilisateur.
Le champ animal[] contient bien les trois valeurs cochées.
Voici la capture de la console pour ce formulaire :

13.4.2 La récupération en PHP

La récupération des données d’un formulaire en PHP se réalise via


les trois tableaux superglobaux :
$_POST contient les données soumises en méthode post.
$_GET contient les données soumises en méthode get, et donc
également les paramètres passés dans l’URL.
$_REQUEST contient toutes les données envoyées par
l’utilisateur, par méthodes get, post ou dans les cookies.
$_FILES contient le tableau de tous les fichiers envoyés par
upload.
Pour notre exemple de formulaire, le tableau $_POST vaut :
Array (
[resolution] => 2560X1440
[nom] => Olivier
[email] => olivier@toutjavascript.com
[naissance] => 1975-11-18
[periode] => 3
[animal] => Array (
[0] => chien
[1] => chat
[2] => poisson
)
[logement] => prop
[texte] => RAS
[reglement] => 1
)

Nous retrouvons bien dans notre tableau $_POST les éléments


envoyés par le formulaire.
Grâce au nommage avec les crochets pour animal[], le champ
animal devient un tableau contenant les choix de l’utilisateur dans le
formulaire.

13.4.3 La récupération en Node.js

Les données envoyées par le navigateur vers le serveur sont


récupérées dans les objets :
query pour les données envoyées en méthode GET.
params pour les données envoyées en méthode POST.
Une introduction à Node.js est présentée dans le chapitre dédié à
partir d’ici.
14

Les appels AJAX

Objectif
Dans ce chapitre, avec les appels AJAX (Asynchronous
JAvascript & Xml), nous allons étudier une fonctionnalité
fondamentale de la programmation web aujourd’hui. Le mot le
plus important dans l’acronyme est bien « Asynchronous ».
Ces appels permettent de communiquer entre le client et le
serveur de manière transparente, sans rechargement de page
et sans interruption dans la navigation.

_ 14.1 APPELS ASYNCHRONES


14.1.1 L’intérêt d’AJAX

Le principe AJAX est utilisé partout aujourd’hui. Voici quelques


exemples d’usage :
Vérification de disponibilité d’un pseudo lors d’une inscription.
Sauvegarde automatique régulière d’un brouillon pendant la
rédaction d’un message ou d’un email.
Upload de fichiers pendant la saisie de formulaire.
Recherche et affichage des notifications en temps réel sur les
réseaux sociaux.
Messagerie instantanée de type chat.
Mise à jour en temps réel de cours de Bourse.
Récupération de données provenant de sources externes.
Chargement automatique pour un effet de scroll infini.
Les possibilités sont quasi illimitées, avec un objectif simple : fournir
de l’information en temps réel à l’utilisateur sans bloquer ses
interactions.

14.1.2 L’objet XMLHttpRequest

Tous les appels AJAX utilisent l’objet XMLHttpRequest, qui définit le


type d’appel et les données à envoyer, détecte les différentes étapes
de l’appel et reçoit la réponse du serveur.

Ne vous inquiétez pas du préfixe XML. AJAX est très souple et


peut récupérer des données de tout type, en particulier du JSON,
bien plus facile à manipuler que le format XML.

◆ Les méthodes

Un objet ajax de type XMLHttpRequest démarre une requête AJAX


avec la méthode open() :
ajax.open(String typeEnvoi, String url, Boolean asynchrone)

Le paramètre typeEnvoi vaut GET ou POST. Le paramètre url est


l’adresse web à appeler.
La méthode setRequestHeader() permet d’ajouter des paramètres
de configuration dans l’en-tête HTTP, comme le format des
caractères :
ajax.setRequestHeader(String entete, String valeur)

La méthode send() déclenche l’appel AJAX :


ajax.send(String parametre)
Une fois la requête terminée, la méthode getAllResponseHeaders()
retourne tous les en-têtes HTTP de retour :
String ajax.getAllResponseHeaders()

◆ Les propriétés

L’objet ajax possède trois propriétés renseignées au fur et à mesure


de l’avancement de la requête :
readyState est un entier entre 0 et 4 qui indique l’avancement
de la requête :
• 0 : Objet ajax non initialisé
• 1 : Objet ajax initialisé avec open()
• 2 : En-têtes de réponse disponibles
• 3 : Données reçues mais accessibles en partie
• 4 : Données complètement accessibles dans response
response est le contenu de la réponse envoyée par le serveur.
status est le code retour du serveur à l’issue de la requête. Les
codes retour sont standardisés dans le web. Parmi les codes
importants que vous risquez de rencontrer :
• 200 : OK, tout s'est bien passé
• 404 : Ressource introuvable
• 401 : Accès interdit
• 500 : Erreur de traitement sur serveur
La propriété timeout définit le temps alloué à la requête.

◆ Les événements

L’objet ajax écoute deux événements permettant le suivi de


l’évolution de la requête :
onload est la fonction à déclencher quand l’appel est terminé.
onreadystatechange est la fonction à déclencher à chaque fois
que la propriété readyState est incrémentée, et donc que
l’avancement de la requête progresse.

14.1.3 Créer un appel AJAX en JavaScript

Grâce à l’objet XMLHttpRequest, écrivons notre premier appel AJAX


vers le service en ligne cryptocompare.com, qui fournit le cours du
Bitcoin en temps réel. Pour cet exemple, nous allons créer l’appel et
en suivre les différentes étapes.
function getCours() {
/* Appel AJAX vers cryptocompare.com */
var ajax = new XMLHttpRequest();
console.log("readyState après new : "+ajax.readyState);
/* Détection de l'avancement de l'appel */
ajax.onreadystatechange=function() {
console.log("readyState a changé et vaut : "+ajax.readyState)
}
/* Détection de la fin de l'appel */
ajax.onload = function() {
console.log("Appel AJAX terminé");
console.log(" status : "+this.status);
console.log(" response : "+this.response);
if (this.status == 200) { /* Le service a bien répondu */
var json=JSON.parse(this.response); // Convertir le retour JSON
var eur=formatMontant(json.EUR);
var dt=new Date();
document.getElementById("cours").innerHTML=eur+" &euro;";
document.querySelector("div#horo").innerHTML="Maj "+dt.toLocaleString();
}
}
var url="https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=EUR";
ajax.open("GET", url, true);
ajax.send();
}
À l’appel de la fonction getCours(), l’objet ajax est déclaré, avec les
événements de suivi de l’avancement et de fin de chargement. La
console affiche :

readyState après new : 0


readyState a changé et vaut : 1
readyState a changé et vaut : 2
readyState a changé et vaut : 3
readyState a changé et vaut : 4
Appel AJAX terminé
status : 200
response : {"EUR":5275.96}

Avec un peu de CSS et de DHTML, l’appel affiche sur l’écran le


cours du Bitcoin et la date de mise à jour :

Ici, l’appel AJAX vers l’API de récupération de cours se fait en mode


get et via l’URL :
https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=EUR

Une API (Application Programming Interface) est un ensemble


standardisé de fonctionnalités qui permet de communiquer avec
d’autres services ou logiciels.

14.1.4 Créer un appel plus fiable

L’appel précédent a été conçu sans tenir compte du fait que des
événements perturbateurs peuvent se produire. L’API du site
cryptocompare.com peut être en panne et ne pas répondre ou
retourner du contenu non valide.
Pour garantir un fonctionnement cohérent dans toutes les situations,
il est nécessaire de détecter :
Une erreur dans l’appel, en utilisant l’événement onerror sur
l’objet XMLHttp Request.
Un mauvais contenu JSON retourné par l’appel, en utilisant le
bloc try catch.
Vous trouverez plus de détails sur la gestion de ces erreurs dans le
chapitre suivant, à partir d’ici.

14.1.5 Gérer le timeout

Les appels AJAX gèrent les délais d’expiration, appelés timeout. En


définissant la propriété timeout, exprimée en nombre de
millisecondes, le navigateur interrompra l’appel AJAX si aucune
réponse n’est reçue avant ce délai. L’événement ontimeout est alors
déclenché et un nouvel essai d’accès au service est lancé après
cinq secondes :
/* Détection du timeout */
ajax.ontimeout=function() {
console.log("Le service n'a pas répondu à temps : nouvel essai dans 5 sec");
/* Relancer l'appel 5 secondes plus tard */
setTimeout("getCours()", 5000);
}

Retrouvez l’appel complet sécurisé à la page http://tjs.ovh/bitcoin.


Les appels AJAX avec le framework jQuery sont traités ici.

14.1.6 Envoi de données complexes

Naturellement, les appels AJAX peuvent transmettre des données


plus complexes. Grâce à la méthode post, ils peuvent envoyer tout
ce qui est contenu dans un formulaire.
Le type FormData permet de représenter en JavaScript le contenu
d’un formulaire. Un objet FormData peut être créé vide avec :
var data=new FormData();

La méthode append() permet d’ajouter un couple propriété/valeur à


l’objet :
data.append("currency", "EUR");

Un objet FormData peut aussi reprendre directement le contenu d’un


formulaire existant sur la page lors de sa création :
var monForm=document.querySelector("form#monForm");
var data=new FormData(monForm);

L’objet FormData est passé dans le paramètre de send() pour être


transmis dans la requête :
ajax.open('POST', '/api/service.php', true);
var data=new FormData();
data.append("currency", "EUR");
ajax.send(data);

Un exemple réel d’utilisation de FormData est proposé dans le


chapitre suivant, pour l’enregistrement d’un journal des erreurs
JavaScript.

_ 14.2 LE TRAITEMENT CÔTÉ SERVEUR


Si vous souhaitez utiliser AJAX pour communiquer avec votre
serveur, vous aurez besoin d’un minimum de programmation en
langage serveur. Voici deux exemples rapidement abordés dans ce
livre, mais disponibles intégralement en ligne.

14.2.1 Récupérer l’adresse IP en JavaScript


Le JavaScript ne permet pas de connaître l’adresse IP du visiteur.
Seul un langage de type serveur donne accès à cette information.
Pour afficher l’adresse IP en JavaScript, un appel AJAX est la seule
solution.
Nous allons utiliser la page www.toutjavascript.com/api/get-my-
ip.php, qui retourne uniquement l’adresse IP de son utilisateur.
En PHP, le code source est simplement :
print(getenv("REMOTE_ADDR"));

La récupération de l’adresse IP par un appel AJAX s’écrit :


function getIP() {
var ajax = new XMLHttpRequest();
ajax.onload = function() {
console.log("Appel AJAX terminé");
console.log(" status : "+this.status);
console.log(" response : "+this.response);
if (this.status == 200) { /* Le service a bien répondu */
var ip=this.response;
var reg=new RegExp("^[0-9]{1,3}.+[0-9]{1,3}.+[0-9]{1,3}.+[0-9]{1,3}$")
if (reg.test(ip)) {
document.getElementById("ip").innerHTML=ip;
} else {
document.getElementById("ip").innerHTML="Erreur retour";
}
}
}
/* Préparation de la requête et envoi */
var url="/api/get-my-ip.php";
ajax.open("GET", url, true);
ajax.send();
}

Une expression régulière (voir RegExp) s’assure que le retour


correspond bien au format d’une adresse IP. Si le résultat est
correct, l’IP s’affiche dans le div#ip.
Retrouvez le script complet à l’adresse http://tjs.ovh/ip.
14.2.2 Envoyer des fichiers en mode asynchrone

Nous avons vu au chapitre des formulaires la gestion de l’upload de


fichiers avec le champ input type="file". AJAX permet d’envoyer les
fichiers en mode asynchrone, pendant que l’utilisateur poursuit la
saisie de son formulaire. Ce principe est maintenant généralisé pour
l’envoi de photos sur les réseaux sociaux ou de documents
justificatifs sur les sites bancaires ou administratifs. AJAX permet en
plus d’afficher une barre de progression de l’envoi vers le serveur.
Pour déclencher l’envoi du fichier par AJAX, nous utilisons une zone
de drag and drop. Dès que le fichier est sélectionné, l’envoi via
AJAX est déclenché avec l’appel à la fonction :
function uploadFileAjax(file, numero) {
var ajax=new XMLHttpRequest();
/* Détection de la fin de l'appel */
ajax.onload = function() {
if (this.status == 200) { /* Le service a bien répondu */
document.querySelector("div#progress"+numero).innerHTML="Envoi réussi";
}
}
/* Détection d'une erreur */
ajax.onerror = function() {
document.querySelector("div#progress"+numero).innerHTML="Echec d'envoi";
}
/* Préparation de la requête et envoi */
ajax.open("POST", "/api/uploadFileAjax.php", true);
var data=new FormData();
console.log(file);
data.append("monFichier", file);
ajax.send(data);
}

La prévisualisation affiche, en plus de la vignette et du nom de


l’image, une barre de progression. Une barre de progression est
simplement un bloc div contenant un span avec une couleur de
fond :
<div class="progress" id="progress0">
<span class="progress" style="width:10%"></span>
</div>

La détection de l’avancement de l’upload nécessite un autre appel


AJAX, qui sera suivi via l’événement onprogress de l’objet upload :
ajax.upload.onprogress = function(evt) {
/* Le navigateur supporte le suivi de progression */
if (evt.lengthComputable) {
var pct = (evt.loaded / evt.total) * 100;
var bar=document.querySelector("#progress"+numero).querySelector("span");
bar.style.width=Math.round(pct)+"%";
console.log(pct + " % envoyé");
}
};

La propriété lengthComputable de l’événement est un booléen qui


indique si le navigateur peut suivre la progression de l’envoi du
fichier.
Le script complet est disponible à l’adresse http://tjs.ovh/ajaxFile.

_ 14.3 LES CONTRAINTES LIÉES À AJAX


Si AJAX répond à de nombreux besoins ergonomiques, il n’en reste
pas moins une discipline assez complexe, avec de nombreuses
contraintes. Ce dernier paragraphe balaie l’ensemble des difficultés
auxquelles un développeur web peut être confronté.

14.3.1 La sécurité
La sécurité est l’élément essentiel des appels AJAX. Les navigateurs
ont introduit par défaut des processus de blocage d’appel assez
stricts, pour empêcher les attaques par XSS (Cross-Site Scripting).
Ces attaques utilisent une faille de sécurité permettant d’injecter du
contenu ou du code non légitime. L’utilisateur est alors soumis à de
fausses informations ou plus souvent à du code JavaScript
dangereux, destiné à lui dérober des données de connexion, à lui
afficher des publicités ou à le rediriger vers un site pirate.
Ainsi, par défaut, un appel AJAX vers un domaine différent du
domaine d’origine est interdit.

Attention, pour les navigateurs, un sous-domaine ou un protocole


différents sont considérés comme dangereux et donc interdits
d’office.

Pour autoriser les appels vers des domaines externes, il faut


indiquer dans l’en-tête HTTP de la ressource appelée les domaines
autorisés grâce à la propriété Access-Control-Allow-Origin :
En PHP, pour autoriser tous les domaines, on ajoute :
header("Access-Control-Allow-Origin: *"); /* Tous les domaines */

Pour autoriser uniquement mondomaine.fr :


header("Access-Control-Allow-Origin: https://mondomaine.fr");
header("Vary: Origin"); /* Pour indiquer que l'origine change les droits*/

14.3.2 L’encodage des caractères

L’encodage des caractères entre le navigateur et le serveur peut


rapidement virer au cauchemar. Par défaut, les appels AJAX
envoient vers le serveur toutes les données au format UTF-8.
Si votre site n’est pas encore au format UTF -8, il va falloir
transcoder les données à la réception et à l’émission.
En PHP, la fonction de conversion du format UTF -8 vers le format
courant du serveur est utf8_decode().
Le format UTF-8 est vivement recommandé. Sans aucun effort de
programmation, il permet de mélanger différentes typologies de
caractères sur la même page (par exemple du chinois sur une
page en français). La migration d’un site existant au format
français (charset=iso-8859-1) vers UTF-8 demande un gros
travail de conversion de tous les fichiers sources et des bases de
données.

14.3.3 AJAX en local

AJAX en local, c’est-à-dire avec un script exécuté directement


depuis le disque dur, avec le protocole file://, ne fonctionnera pas
correctement. Par exemple, le code retour ne sera jamais
correctement renseigné à la valeur 200 identifiant un appel correct,
car aucun serveur web n’est utilisé. Il est recommandé de
programmer et de tester ses scripts avec un véritable serveur web,
soit avec un site distant chez un hébergeur classique, soit via un
serveur local sur une machine virtuelle dédiée.

14.3.4 Charge serveur

La programmation AJAX a comme conséquence de multiplier très


facilement les appels vers le serveur. Avec une audience
significative, le serveur peut être soumis à une charge élevée et
entraîner des baisses de performances, des coupures ou des
allongements de temps de réponse.
Les temps de réponse d’un site sont un des multiples éléments
entrant en compte pour le classement des résultats dans les
moteurs de recherche.

14.3.5 Référencement

Aujourd’hui, tous les robots d’indexation des moteurs de recherche


savent parfaitement interpréter le JavaScript, les appels AJAX et leur
effet sur le rendu des pages. L’impact sur le référencement d’un site
utilisant AJAX n’est donc plus significatif.
La question qui reste posée est : L’accès aux informations est-il
possible via une URL directe ? Si aucune URL directe n’est
proposée, le moteur de recherche ne pourra pas la référencer et les
utilisateurs ne pourront pas y accéder directement, ni par moteur de
recherche, ni par partage de liens.

14.3.6 Ergonomie

La programmation AJAX implique une gestion des erreurs nettement


plus poussée. Contrairement à une erreur de chargement d’une
page web entière qui est signalée par le navigateur, un appel AJAX
qui échoue n’est pas signalé. C’est au développeur d’informer
l’utilisateur que son action n’a pas été correctement enregistrée pour
lui donner la possibilité de la relancer.

Nous verrons ici comment réaliser des appels AJAX avec jQuery.
15

La gestion des erreurs

Objectif
Dans ce chapitre, nous allons étudier comment repérer et
traiter les erreurs JavaScript. La gestion des erreurs peut
paraître inutile à première vue, car un bon développeur ne
laisse pas de bug ni de code susceptible de produire des
erreurs. Ce chapitre ne traite pas des erreurs issues d’une
programmation bancale mais des erreurs qui surviennent
inévitablement dans l’environnement complexe et imprévisible
de l’Internet.

_ 15.1 LA DÉTECTION D’UNE ERREUR


Le but de ce chapitre est bien de détecter des erreurs liées à
l’environnement web et à ses multiples contraintes :
Coupure de connexion Internet.
Incompatibilité d’un navigateur exotique ou ancien.
Mauvais retour d’un service externe.
Mauvaise saisie d’un utilisateur.

15.1.1 Les erreurs de programmation


Par défaut, le navigateur remonte les erreurs JavaScript détectées,
les affiche dans la console et décide de la poursuite ou non du script
en cours. Une erreur JavaScript a de grandes chances d’interrompre
la fonction ou le traitement de l’événement en cours.
Pour bien comprendre la gestion des erreurs, nous allons en simuler
une évidente, en utilisant une variable inconnue dans une fonction :
function maFonctionAvecErreur() {
console.log("Début de maFonctionAvecErreur()");
variableInconnue++;
console.log("Fin de maFonctionAvecErreur()");
}
maFonctionAvecErreur();

Le script lance la fonction, et la console affiche le message d’erreur :

Le navigateur a interrompu la fonction, qui ne termine pas son


exécution. La console affiche des informations sur le type d’erreur et
son emplacement dans le code source.
Pour empêcher le navigateur de remonter l’erreur et pour la
« capturer » dans notre code, nous utilisons la syntaxe try … catch :
try {
/* Traitements à surveiller */
} catch(err) {
/* Capture de l'erreur éventuelle dans err */
/* Traitements à réaliser si une erreur est détectée dans le bloc try */
}

Pour reprendre l’exécution de notre fonction, nous utilisons :


try {
maFonctionAvecErreur();
} catch(err) {
console.log("Entrée dans le bloc catch(err)");
console.log(getHeritages(err));
}

La console n’a remonté aucune erreur et notre bloc catch s’est bien
exécuté :

Début de maFonctionAvecErreur()
Entrée dans le bloc catch(err)
(3) ["ReferenceError", "Error", "Object"]

15.1.2 Les erreurs à détecter

L’exemple précédent n’a aucun intérêt en pratique. Si une erreur de


variable inconnue est détectée, il faut la corriger dans le code
source.
Imaginons plutôt un service externe qui nous fournit, sous format
JSON, la valeur actuelle du Bitcoin en dollars et en euros. Nous ne
pouvons pas avoir une confiance aveugle dans le résultat fourni par
ce service, car il peut être en maintenance, piraté ou définitivement
fermé. Pour utiliser la chaîne retour, nous devons utiliser un bloc try
afin d’en vérifier son format :
try {
var json=JSON.parse(retour);
console.log("1 BTC = "+json.EUR+" EUR");
} catch(err) {
console.log("Erreur de format de retour du service");
console.log(getHeritages(err));
}

Reportez-vous au chapitre sur la programmation objet pour les


manipulations du format JSON.

Avec retour valant {"USD":6134.06,"EUR":5258.26}, le bloc try est


exécuté normalement et la console affiche :
1 BTC = 5258.26 EUR

En revanche, avec retour valant Service unavalaible, le bloc catch


s’exécute et la console contient :

Erreur de format de retour du service


(3) ["SyntaxError", "Error", "Object"]

Le bloc catch permet d’afficher à l’utilisateur un message


d’information l’invitant à revenir dans quelques minutes. On peut
aussi imaginer un appel AJAX, afin que le gestionnaire du site soit
informé de la panne du prestataire.

_ 15.2 L’OBJET ERROR


15.2.1 Les propriétés de Error

Grâce à notre fonction getHeritages() appelée sur err, nous


observons qu’une erreur hérite de l’objet principal Error. Chaque
typologie d’erreur est d’un type particulier, par exemple InternalError,
ReferenceError, EvalError, SecurityError ou SyntaxError.
Le type Error contient les propriétés :
name est le nom du type d’erreur.
message est un message d’erreur dans un format
compréhensible.
fileName n’est pas reconnu par tous les navigateurs et contient
le nom du fichier responsable de l’erreur.
lineNumber n’est pas reconnu par tous les navigateurs et
contient le numéro de ligne dans le fichier responsable de
l’erreur.
columnNumber n’est pas reconnu par tous les navigateurs et
contient l’indice du caractère de la ligne responsable de
l’erreur.
stack (empilement) est le texte d’empilement de la source
d’erreur qui apparaît dans la console.
En détectant une erreur sur l’évaluation de JavaScript avec eval(),
nous pouvons afficher les différentes propriétés :
try {
eval("Code JavaScript invalide");
} catch(err) {
console.log(err.name); // SyntaxError
console.log(err.message); // Unexpected identifier
console.log(err.fileName); // http://tjs.local/error.html ou undefined
console.log(err.lineNumber); // 1 ou undefined
console.log(err.columnNumber); // 5 ou undefined
console.log(err.stack); // SyntaxError: Unexpected identifier at error.html:48
}

15.2.2 Créer ses propres erreurs

Le JavaScript permet de créer ses propres erreurs et de les envoyer


au navigateur, comme s’il s’agissait d’une erreur native du langage.
L’instruction throw (« lancer » en français) propage l’erreur :
function checkPseudo(pseudo) {
/* Un pseudo doit contenir entre 3 et 20 caractères alphanumériques */
var reg=new RegExp("^[0-9a-z]{3,20}$","gi");
if (!reg.test(pseudo)) {
var err=new RangeError("Le pseudo "+pseudo+" n'est pas valide");
throw err;
}
}
try {
checkPseudo("Mister$"); // Le caractère $ n'est pas autorisé
} catch(err) {
console.log(err);
}

Ici une erreur a été créée avec le constructeur RangeError() et


envoyée par throw. L’erreur est détectée dans le bloc try, et le bloc
catch est exécuté.
On parle dans ce cas d’une exception définie par le développeur.

15.2.3 Détection des erreurs par événement

Le navigateur déclenche un événement onerror à chaque erreur


détectée. Il est ainsi possible d’écouter toutes les erreurs remontées
par le navigateur sur l’objet window :
window.addEventListener("error", function(evt) {
console.log("Event onerror");
console.log(getHeritages(evt));
console.log(evt.error.stack);
});

En ajoutant une erreur dans le code source de la page, on obtient


dans la console :

Event onerror
(3) ["ErrorEvent", "Event", "Object"]
ReferenceError: fonctionInconnue is not defined at error.html:82

L’événement remonté est de type ErrorEvent et contient la propriété


error avec toutes les informations de l’erreur qui a déclenché
l’événement.

Notez que les erreurs capturées dans un bloc try ne déclenchent


pas l’événement onerror.

15.2.4 Journal des erreurs JavaScript


Grâce à l’écoute des erreurs, nous pouvons imaginer enregistrer un
journal de toutes les erreurs JavaScript rencontrées pendant la
navigation de nos utilisateurs.
En modifiant notre fonction d’écoute de onerror et en lui ajoutant un
appel vers le serveur via AJAX, nous allons pouvoir constituer un
journal (on parle de log en anglais) de toutes les erreurs JavaScript.
En analysant ce journal, nous pourrons détecter les erreurs
fréquentes ou les navigateurs les plus concernés et ainsi améliorer
le site.

Attention, en raison des contraintes de la RGPD, il n’est pas


autorisé d’enregistrer des données nominatives et personnelles
sans en informer précisément vos utilisateurs. Même une simple
adresse IP est considérée comme une information personnelle
sensible. Pour limiter tout risque règlementaire, nous utilisons une
fonction de hashage de l’adresse IP pour la rendre impossible à
décrypter.

◆ L’envoi des données via JavaScript

Ajoutons l’événement onerror sur window, qui déclenche un appel


AJAX via post vers la page /api/logerrorjs.php.
window.addEventListener('error', function(evt) {
/* Appel AJAX vers serveur */
console.log('Log error start');
var ajax = new XMLHttpRequest();
ajax.onload = function() {
if (this.status == 200) {
console.log('Log error : '+this.response);
}
};
ajax.open('POST', '/api/logerrorjs.php', true);
var data=new FormData();
data.append('agent', navigator.userAgent);
data.append('name', evt.error.name);
data.append('message', evt.error.message);
data.append('stack', evt.error.stack);
data.append('url', window.location.href);
ajax.send(data);
});

Nous utilisons un objet data de type FormData pour envoyer sur le


serveur les différents éléments de l’erreur à enregistrer dans le
journal (IP, nom et message de l’erreur et page concernée par
l’erreur).
Les grands principes AJAX ont été décrits au chapitre précédent, à
partir d’ici.

◆ La réception côté serveur

La page logerrorjs.php récupère les données envoyées dans le


tableau $_POST et les enregistre dans une table de base de
données SQL.
Grâce à cette fonctionnalité, j’ai par exemple détecté sur mon site
toutjavascript.com l’erreur suivante :
Champ Valeur
dt 2017-09-28 16:10:38
hashIP 766a4093cca3c40fbebcde52c7dc41d8
Mozilla/5.0 (compatible, MSIE 11, Windows NT 6.3;
agent
Trident/7.0; rv:11.0) like Gecko
name ReferenceError
messag
AffBasConseil is not defined
e
ReferenceError: AffBasConseil is not defined
stack at
http://www.toutjavascript.com/savoir/savoir23.php3:165:
8
URL http://www.toutjavascript.com/savoir/savoir23.php3
Cette information permet de détecter que, sur certaines pages de
tutoriels, la fonction JavaScript affBasConseil() n’est pas (ou plus)
déclarée. Voilà une erreur facile à corriger.

L’avantage de l’enregistrement de toutes les erreurs JavaScript


rencontrées par les utilisateurs est que nous pouvons profiter de
la navigation naturelle des internautes et de la variété de leur
équipement pour identifier sans effort toutes les erreurs du site.
16

Les cookies et l’objet Storage

Objectif
Dans ce chapitre, nous allons étudier comment stocker des
informations sur le poste de l’utilisateur. La méthode la plus
connue utilise naturellement les cookies. Il existe cependant
une autre solution permettant de stocker de grandes quantités
d’informations.

_ 16.1 LES COOKIES


16.1.1 Utilité des cookies

Les cookies sont des données partagées entre le navigateur et le


serveur via le protocole HTTP. Les cookies sont indispensables pour
réaliser les traitements suivants :
Enregistrer un identifiant de session.
Enregistrer la date de dernière consultation.
Enregistrer un code permettant de reconnecter
automatiquement un utilisateur à sa prochaine visite.
Reconnaître un utilisateur pour la mesure d’audience.
Reconnaître un utilisateur pour lui afficher des informations de
ses réseaux sociaux.
Afficher une seule fois un message à un utilisateur.
Suivre (tracking) un utilisateur pour déterminer son profil de
consommation du web.

Les cookies ne sont pas dangereux et ne peuvent contenir aucun


code malveillant. Le véritable risque des cookies est qu’ils
permettent de suivre les internautes dans toute leur navigation sur
le web et donc de connaître leur profil très précisément, au
détriment parfois du respect de la vie privée.

16.1.2 La réglementation

La réglementation européenne s’est emparée du sujet de la vie


privée des internautes. La première étape a été l’obligation
d’informer les utilisateurs quant à l’usage de cookies sur les sites
consultés, avec les insupportables messages nous demandant de
confirmer l’acceptation des cookies.
La seconde étape, en cours de déploiement avec le RGPD, consiste
à donner le choix des cookies que l’utilisateur autorise.

Il est cocasse de remarquer que l’affichage des messages et


l’enregistrement des préférences, même en cas de refus, utilisent
les cookies.

16.1.3 Les cookies tiers

Les cookies tiers sont des cookies enregistrés par des services
externes au site consulté. Ils sont à la base de l’agacement des
utilisateurs et des risques sur la vie privée. Les services externes qui
déposent des cookies tiers sont le plus souvent des régies
publicitaires ou des réseaux sociaux (qui ne sont en fait que des
régies publicitaires pour leur propre compte). La force de frappe de
ces services, qui touchent des centaines de millions d’utilisateurs,
leur permet de cibler tout le monde, tout le temps.
C’est ainsi que nous voyons des publicités de retargeting
(« reciblage publicitaire » en français) pour nous vendre une paire de
chaussures.

16.1.4 Manipuler les cookies

Les cookies sont manipulés avec la propriété cookie de document.


La chaîne de caractères cookie a un comportement très particulier,
car ce qu’on y écrit est très différent de ce qu’on y lit.
Sa lecture retourne une chaîne de caractères contenant tous les
cookies enregistrés pour le domaine en cours :
tousLesCookies = document.cookie

La chaîne tousLesCookies est du type :


nom1=valeur1; …; nomN=valeurN

Les couples nom=valeur sont séparés par un point-virgule et un


espace.
En revanche, l’affectation de la propriété cookie enregistre un
nouveau cookie (ou modifie un cookie existant), sans aucun impact
sur les autres cookies déjà présents :
document.cookie = nouveauCookie

La chaîne nouveauCookie possède une structure très précise :


nom=valeur affecte une valeur à un nom, avec la partie valeur
encodée grâce à la fonction encodeURIComponent() qui gère
les caractères spéciaux.
;expires=dateAuFormatISO est une partie optionnelle qui
indique la date d’expiration du cookie. Si expires n’est pas
fourni, le cookie sera détruit à la fin de la session.
;domain=domaine est aussi optionnel et indique le domaine de
validité du cookie (domaine.com ou .domaine.com).
;path=chemin, toujours optionnel, indique si le cookie est
restreint à un chemin interne du site. Par défaut, concerne tout
le domaine.
;secure n’autorise le cookie que si le protocole de
communication est bien fixé à HTTPS.

◆ Enregistrer un cookie

Nous venons de voir que l’enregistrement d’un cookie impose une


structure rigoureuse dans la chaîne de caractères. Nous pouvons
donc utiliser une fonction setCookie() :
function setCookie(name, value, expires="", path="", domain="", secure=0) {
document.cookie=name+"="+encodeURIComponent(value)+
((expires=="") ? "" : (";expires="+expires.toUTCString()))+
((path=="") ? "" : (";path="+path))+
((domain=="") ? "" : (";domain="+domain))+
((secure==true) ? ";secure" : "");
}

Dans cette fonction, seuls les deux premiers paramètres sont


indispensables. La fonction construit la chaîne en fonction des
paramètres supplémentaires optionnels.
Quelques exemples d’enregistrement de cookies :
/* Cookie de session */
setCookie("nom", "Jean-Claude");
var dt1An=new Date();
dt1An.setTime(dt1An.getTime()+(365*24*3600*1000));
/* Durée de vie 1 an */
var today=new Date();
setCookie("firstVisite", today.getTime(), dt1An);
/* Et un cookie de durée sur tout le domaine en https */
setCookie("IDuser", "user544572", dt1An, "/", "www.toutjavascript.com", true);
Notez que tous les navigateurs proposent des interfaces pour
lister les valeurs des cookies du site en cours de consultation. Sur
Google Chrome, il suffit de cliquer sur l’icône devant l’URL du site
dans la barre de navigation pour obtenir des informations
générales sur le site, les cookies et les différentes autorisations
accordées.

L’exemple à la page http://tjs.ovh/cookie affiche un message à


l’écran une seule fois grâce à un cookie.
Le navigateur donne ces informations pour les trois cookies ajoutés :

◆ Lire un cookie

La recherche de la valeur d’un cookie dans toute la chaîne de


caractères cookie est assez fastidieuse à réaliser. Pour les trois
cookies créés précédemment, document.cookie contient :

nom=Jean-Claude; firstVisite=1531048355876;
IDuser=user544572

Pour récupérer tous les cookies de la page en cours de consultation,


la fonction allCookies() utilise la fonction split("; ") et retourne un
tableau associatif nom/valeur :
function allCookies() {
/* Le séparateur entre cookies est [; ] */
var tab=document.cookie.split("; ");
var cookies=[];
for (var i=0; i<tab.length; i++) {
var name=tab[i].substring(0, tab[i].indexOf("="));
var value=tab[i].substring(tab[i].indexOf("=")+1);
cookies[name]=value;
}
return cookies; /* Tableau associatif */
}

Voici ce que contient le tableau de retour pour les trois cookies


enregistrés :

[nom: "Jean-Claude", firstVisite: "1531054987600", IDuser:


"user544572"]
IDuser: "user544572"
firstVisite: "1531054987600"
nom: "Jean-Claude"

La création d’une fonction getCookie(name) facilite la recherche de


la valeur d’un cookie name :
/* Retourne la valeur du cookie name ou null s'il n'existe pas*/
function getCookie(name) {
var cookies=allCookies();
if (cookies[name]) {
return decodeURIComponent(cookies[name]);
} else {
return null
}
}
Il n’est pas possible d’obtenir les informations d’expiration d’un
cookie avec la propriété cookie. Seuls les couples nom=valeur
sont accessibles.

◆ Effacer un cookie

Affecter une valeur vide à un cookie ne le supprime pas. Pour


supprimer définitivement un cookie, il faut l’écrire de nouveau avec
une valeur d’expiration dans le passé :
var dtOld=new Date("2000-01-01"); /* Une date ancienne */
setCookie("nom", "", dtOld); /* Appel avec la date en expiration */

16.1.5 Transmission des cookies vers le serveur

Tous les cookies d’un domaine sont transmis vers le serveur dans la
rubrique Request Headers de l’en-tête HTTP. Pour nos trois cookies
en cours, la console du navigateur affiche bien :

Les cookies sont bien reçus par le serveur et peuvent être manipulés
pour personnaliser l’affichage de la page.
Le serveur peut aussi écrire des cookies avec la fonction setcookie()
en PHP par exemple. Un cookie créé par le serveur est envoyé à
son tour dans l’en-tête HTTP et écrit par le navigateur sur le disque
dur de l’utilisateur.
_ 16. 2 LE STOCKAGE DE DONNÉES
LOCALES
16.2.1 L’objet Storage

Nous avons vu que les cookies transitent systématiquement entre le


navigateur et le serveur dans les en-têtes HTTP. Cette particularité
est très utile pour transmettre des informations tout au long de la
navigation. Certaines données n’ont d’utilité que côté utilisateur,
ainsi les objets de type Storage permettent de stocker dans le
navigateur des données associées au domaine en cours de
consultation, sous la forme d’un couple clé : valeur.
La plupart des navigateurs supportent cette fonctionnalité mais il est
utile de vérifier l’existence de l’objet avec la syntaxe :
if (typeof Storage !== "undefined") {
// Fonctionnalité disponible
}

La propriété localStorage de window est de type Storage et stocke


des données de manière durable. La propriété sessionStorage est
équivalente mais la durée de vie des données est limitée à la
session courante.
L’enregistrement d’un couple clé : valeur dans un objet Storage est
réalisé avec setItem() :
localStorage.setItem(String cle, String valeur)

La méthode getItem() retourne la valeur d’une clé :


String localStorage.getItem(String cle)

La méthode removeItem() supprime une clé :


localStorage.removeItem(String cle)

L’effacement de tous les couples clé : valeur est réalisé avec clear().
Le nombre de clés enregistrées est retourné par la propriété au nom
classique length. Cette propriété permet de parcourir toutes les clés
enregistrées avec key() :
String localStorage.key(Integer numCle)

Attention, comme pour les cookies, il n’existe aucune garantie


que les données stockées seront conservées par l’utilisateur.
Celui-ci peut décider de vider les Storage quand il le souhaite, ou
même automatiquement, à chaque fermeture d’une session en
mode navigation privée.

16.2.2 Les utilisations possibles

Les navigateurs définissent une taille maximale disponible pour les


données locales stockées. Généralement, la limite est de quelques
millions d’octets (Mo), ce qui permet d’enregistrer de grandes
quantités d’informations sous forme de texte.
Voici quelques types de données qu’il est possible d’enregistrer dans
le Storage :
Adresses favorites dans un logiciel de cartographie.
Données récurrentes pour un prochain lancement plus rapide
du site.
Préférences d’affichage du site.
Panier de commande sauvegardé.
Saisies d’un utilisateur pendant que son smartphone est hors
ligne.
Un exemple de stockage d’une liste de pays dans le navigateur est
visible sur http://tjs.ovh/storage.
_ 16.3 LES DIFFÉRENCES ENTRE COOKIE
ET L’OBJET STORAGE
16.3.1 Les différences

Si le stockage de données via cookie ou via Storage permet


d’enregistrer des informations sur le navigateur, les deux techniques
présentent des différences notables dans leur comportement :
Cookie Storage
Pas de gestion
Durée de vie Fixée à la création native de la durée
de vie
Autour de 2 Mo,
Limite de taille Autour de 2 ko paramétrable par
l’utilisateur
Oui, tous les cookies
Lecture sur le
sont transmis dans les Non
serveur
en-têtes HTTP
Oui, tous les langages
Écriture par le serveur peuvent
Non
serveur envoyer un cookie dans
le navigateur
Risques En local et pendant Uniquement en
d’interception le transfert HTTP local

16.3.2 Les similitudes

Parmi les réelles similitudes entre cookie et Storage, il faut noter :


Cookie Storage
Type de données Données texte
Limitation d’accès Accès réservé au
domaine du site en
cours de consultation
Le stockage des
Fiabilité de
données n’est pas
conservation
garanti
Au final, cookie et Storage sont deux techniques qui ont toute leur
place dans la programmation web. Le choix dépend du type de
données à stocker.
17

La géolocalisation

Objectif
Dans ce court chapitre, nous allons étudier comment obtenir
la géolocalisation précise d’un internaute, qu’il utilise une
connexion Internet fixe ou le réseau mobile, que son
navigateur soit équipé d’un GPS ou non.

_ 17.1 CONFIDENTIALITÉ ET SÉCURITÉ


Les données de géolocalisation étant par nature privées et
sensibles, les navigateurs imposent des contraintes fortes pour
l’usage des fonctionnalités liées à l’objet geolocation :
Information auprès de l’utilisateur que le site demande sa
géolocalisation.
Accès à la position après autorisation explicite.
Autorisation d’accès accordée uniquement pour un domaine.
Accès uniquement si le site utilise le protocole HTTPS.
Chaque navigateur propose sa propre interface d’information et de
demande d’autorisation. Il n’est pas possible de la modifier.
_ 17.2 OBTENIR LA POSITION ACTUELLE
L’objet geolocation est lié à navigator. Il possède la méthode
principale d’accès à la position getCurrentPosition(), qui retourne un
objet de type Position :
Position navigator.geolocation.getCurrentPosition(Function succes [, Function erreur,
PositionOptions options])

La méthode attend en premier paramètre la fonction à exécuter


quand la position est correctement obtenue.
Le deuxième paramètre, facultatif, est une fonction à exécuter en
cas d’erreur de récupération de la position.
Le dernier paramètre, facultatif, est un objet PositionOptions fixant
les paramètres de recherche de la position :
enableHighAccuracy, booléen qui indique que la position doit
être la plus précise possible, sachant qu’une position plus
précise entraîne un temps de recherche plus long (false par
défaut).
timeout, entier indiquant en millisecondes la durée maximale de
recherche (pas de limite par défaut).
maximumAge, entier indiquant en millisecondes l’âge de la
dernière position en cache qui peut être retournée (pas de
limite par défaut).
Pour la majorité des navigateurs, un appel sur une page non
sécurisée retourne une erreur du type :

Only secure origins are allowed (see: https://goo.gl/Y0ZkNV).

Un appel sur une page sécurisée affichera un message informant


que le domaine en cours demande à obtenir l’emplacement
physique et différentes options pour l’utilisateur :
Autoriser une seule fois l’accès.
Autoriser systématiquement l’accès.
Ne jamais autoriser l’accès et supprimer toute nouvelle
demande.
Voici un exemple de message d’information :

Toutes les autorisations et interdictions sont ensuite enregistrées


dans le navigateur et listées dans les options, généralement dans un
onglet gérant la confidentialité.
Voici un exemple de fonction de récupération de la position de
l’internaute :
function getMyPosition() {
navigator.geolocation.getCurrentPosition(function(position) {
console.log("Position trouvée : Latitude="+position.coords.latitude+ "
Longitude="+position.coords.longitude);
console.log(position.coords);
}, function(error) {
console.log("Erreur de géoloc N°"+error.code+" : "+error.message);
console.log(error);
}, {
timeout: 2000,
maximumAge: 60000
});
}

Le retour en cas de succès est un objet Position contenant un sous-


objet coords contenant lui-même toutes les informations qui
permettent d’identifier l’emplacement physique de l’utilisateur. Dans
la console, apparaît par exemple :
Coords
accuracy: 65
altitude: 53.27738571166992
altitudeAccuracy: 10
latitude: 86.27957771263072
longitude: -0.9063173302260534

Retrouvez ce script à l’adresse http://tjs.ovh/geolocation.

_ 17.3 OBTENIR LES POSITIONS


RÉGULIÈREMENT
La méthode watchPosition() permet de déclencher une fonction à
chaque changement de position de l’utilisateur :
Integer navigator.geolocation.watchPosition(Function succes [, Function erreur,
PositionOptions options])

L’identifiant retourné permet de stopper la surveillance des


déplacements avec clearWatch() :
navigator.geolocation.clearWatch(Integer idWatch);

Cette fonctionnalité est utilisée sur des appareils mobiles, dans des
applications de guidage GPS ou de suivi d’activités physiques. Elle
entraîne une plus grosse consommation d’énergie.

_ 17.4 LES SCRIPTS DE CARTOGRAPHIE


Des fournisseurs externes proposent des services de cartographie,
généralement gratuits pour une utilisation réduite, permettant
d’utiliser directement les retours de position :
Le plus connu : Google Maps JS API.
Le service de Nokia : Here JavaScript API.
Le service de Microsoft : Maps SDK.
Un service français : ViaMichelin JS API.
18

Les notifications

Objectif
Dans ce chapitre, nous allons étudier comment utiliser le
système de notifications du système d’exploitation pour
envoyer des informations aux utilisateurs.

_ 18.1 LE PRINCIPE DES NOTIFICATIONS


Tous les systèmes d’exploitation modernes proposent un système de
notifications ouvert aux applications installées par l’utilisateur. Parmi
les notifications auxquelles nous sommes parfaitement habitués,
nous pouvons lister la réception d’un nouveau message, d’un
nouveau mail, une mise à jour de sécurité, un niveau de batterie
faible, etc.

Un navigateur est une application comme les autres. Il offre donc la


possibilité d’afficher des notifications.
Les notifications peuvent devenir rapidement intrusives et
fastidieuses. Ainsi, les navigateurs demandent systématiquement
une confirmation à l’utilisateur avant d’autoriser un domaine de site
Internet à lui envoyer des notifications.

Les notifications sont gérées par le système d’exploitation et


apparaissent en dehors du navigateur. Il ne faut pas confondre
une notification du système d’exploitation avec les affichages de
message d’information à l’intérieur d’une page web.

_ 18. 2 LES NOTIFICATIONS


EN JAVASCRIPT
18.2.1 L’objet Notification

Très logiquement, le JavaScript propose un objet de type Notification


pour afficher des informations à l’utilisateur. Le constructeur attend
deux paramètres, un titre et un JSON définissant les informations :
new Notification(String titre, JSON options)

Le second paramètre d’options contient les informations importantes


qui seront affichées sur la notification :
tag est l’identifiant de la notification.
lang est le code langue du contenu de la notification.
body est le texte brut, sans balise HTML, à afficher dans la
notification.
icon est l’URL de l’image à afficher dans le corps.
Le paramètre tag est important car il identifie la notification dans le
système d’exploitation et empêche les diffusions multiples de
notifications ayant le même tag.

18.2.2 Les autorisations


La propriété permission indique l’état de l’autorisation d’affichage
des notifications pour le domaine en cours. Les valeurs possibles
sont les chaînes de caractères suivantes :
granted : Autorisation donnée.
denied : Autorisation refusée définitivement.
default : Autorisation pas encore traitée par l’utilisateur.
Notez que la propriété permission n’est pas associée à l’instance de
l’objet mais directement au constructeur. La syntaxe est donc
Notification.permission.
Comme pour de nombreuses fonctionnalités, le navigateur risque de
refuser l’affichage de notification si le site n’utilise pas le protocole
sécurisé HTTPS.
La méthode requestPermission() demande à l’utilisateur s’il souhaite
recevoir les notifications de votre site :
Notification.requestPermission(Function resultat)

La fonction resultat() est appelée lorsque l’utilisateur a répondu au


message de demande d’autorisation. Si l’utilisateur n’y répond pas,
la fonction n’est jamais appelée.
Si la permission est déjà renseignée, l’appel à requestPermission()
n’affiche pas la fenêtre de confirmation et déclenche la fonction
resultat() immédiatement.

Attention, n’utilisez les notifications que si elles apportent une


réelle valeur ajoutée à vos utilisateurs, par exemple dans le cas
d’un site communautaire qui signale les nouveaux messages ou
les nouvelles interactions depuis la précédente visite.

Comme toujours, chaque navigateur propose sa propre interface de


message d’autorisation et il n’est pas possible de la modifier :
Comme pour la géolocalisation, la liste des sites autorisés et
interdits de notifications est modifiable dans les options du
navigateur, dans l’onglet confidentialité/paramètres du contenu.

18.2.3 Les interactions

L’intérêt des notifications est bien sûr de pouvoir déclencher des


interactions, en particulier de lancer une fonction lorsque l’utilisateur
clique dessus. L’événement onclick permet d’ajouter une action au
clic, le plus souvent l’ouverture d’une page :
notif.onclick = function(event) {
event.preventDefault(); /* Pour empêcher l'action par défaut */
window.open("http://www.toutjavascript.com/livre/", "_blank");
}

18.2.4 L’affichage

L’affichage de la notification ayant l’identifiant tag est déclenché :


Si le site est sous le protocole HTTPS (pour la plupart des
navigateurs).
Si la valeur de permission vaut granted.
Si la notification tag n’a pas déjà été affichée.
Dès que l’instance de Notification est créée.
Nous allons créer un script qui propose à l’utilisateur de recevoir des
notifications à chaque nouveau contenu publié dans la référence
JavaScript de toutjavascript.com.
La première étape est de demander à l’utilisateur s’il accepte les
notifications :
if (Notification.permission=="granted") {
sendNotification("Confirmation", "Vous avez accepté les notifications");
} else if (Notification.permission=="denied") {
var info=document.getElementById("info");
info.innerHTML="Vous avez déjà refusé les notifications de TJS";
info.style.display="block";
} else {
Notification.requestPermission(function(result) {
if (result=="granted") {
var body="Vous recevrez des notifications pour les nouveaux contenus";
sendNotification("Première notification", body, "first", "");
}
});
}

Nous définissons une fonction d’envoi des notifications :


function sendNotification(titre, body, tag, url) {
var options={
tag: tag,
body: body,
icon: "https://www.toutjavascript.com/favicon.ico",
lang: "fr"
};
var notif=new Notification(titre, options);
if (url=="") { url="https://www.toutjavascript.com/"; }
notif.onclick=function() {
window.open(url);
}
}

Ce script détecte les différents choix de l’utilisateur et affiche soit des


notifications, soit un div#info si la notification est refusée.
Retrouvez l’exemple complet des notifications à l’adresse
http://tjs.ovh/notification.
Il reste ensuite à gérer sur l’ensemble des pages du site la détection
des nouveaux contenus ajoutés puis à comparer avec la date de
dernière visite, qui peut être enregistrée dans un cookie par
exemple. Si un nouveau contenu est disponible pour l’utilisateur, il
suffit d’appeler la fonction avec :
var body="Click automatique sur un lien avec JavaScript";
var url="https://www.toutjavascript.com/faq/index.php3?ID=103";
sendNotification("Nouvelle FAQ", body, "faq140", url);

18.2.5 À retenir

Les notifications JavaScript via le système d’exploitation ne peuvent


s’afficher que si le navigateur est ouvert. Comme leur affichage
n’apparaît que sur autorisation des utilisateurs, il est impossible de
compter sur elles pour transmettre des informations de manière
fiable.
Grâce à la propriété tag, le développeur n’a pas à se préoccuper du
risque d’envoi de notifications en doublon car le système
d’exploitation s’en charge.
19

Le dessin et les canvas

Objectif
Dans ce chapitre, nous allons apprendre à dessiner sur une
page du navigateur. Le dessin avec HTML5 et JavaScript est
extrêmement puissant : il est possible de réaliser des
graphiques interactifs et même des jeux en 3D. Nous nous
contenterons ici d’étudier les différentes formes de dessins en
2D et leur animation.

_ 19.1 L’ÉLÉMENT CANVAS


Le dessin sur une page web ne peut se construire que dans un
élément HTML Canvas (« toile » en français), défini par la balise
<canvas>. Cet élément se comporte comme une image avec des
dimensions width et height.
<canvas id="myCanvas" width="300" height="100">Browser incompatible</canvas>

Un élément HTMLCanvasElement possède la méthode getContext(),


qui retourne un objet sur lequel nous pourrons appeler les fonctions
de dessins. La méthode getContext("2d") retourne un objet de type
CanvasRenderingContext2D :
CanvasRenderingContext2D myCanvas.getContext(String mode)
19.1.1 Se repérer dans le canvas

Le repérage des positions dans un canvas est classique, avec des


coordonnées cartésiennes (x, y) et l’origine du point (0, 0) situé en
haut à gauche de l’élément.
Dans un canvas, l’unité des angles est le radian. Pour rappel,
2π radians équivalent à 360°, soit un tour complet. Le sens de
rotation est celui des aiguilles d’une montre.
Voici un schéma des coordonnées, réalisé avec les fonctionnalités
de dessin canvas :

Retrouvez le script du schéma à la page http://tjs.ovh/canvas.


La manière de dessiner dans le canvas se rapproche beaucoup du
langage Logo (et de sa célèbre tortue) des années 1980. La
méthode moveTo() déplace le pointeur vers la position (x, y), sans
tracer de trait :
myContext.moveTo(Integer x, Integer y)

La méthode lineTo() dessine une ligne entre la position actuelle du


contexte de canvas et la position (x, y) :
myContext.lineTo(Integer x, Integer y)
On parle de contexte du canvas obtenu par la méthode
getContext() car le dessin conserve et utilise les informations
précédentes, à la fois pour la position du pointeur et pour les
caractéristiques de style de ligne, de remplissage, de police de
caractères, etc.

19.1.2 Tracer des lignes

◆ La définition d’un chemin

Pour tracer des lignes dans un canvas, il faut d’abord définir un


chemin (path en anglais) avec un ou plusieurs segments de ligne.
Un chemin est initialisé avec l’appel à la méthode beginPath().
Avec les méthodes moveTo() et lineTo(), le curseur est déplacé et
des lignes sont ajoutées au chemin.
La méthode closePath() ferme le chemin en traçant une dernière
ligne entre la position actuelle du contexte et la position à
l’initialisation du chemin.
Pour afficher les lignes du chemin à l’écran, il faut appeler la
méthode stroke() (dans le sens de « faire des traits », en français).
Dans les exemples qui suivent, nous allons partir d’un canvas d’une
taille fixée à 300 px par 300 px. Pour tracer le contour du canvas, il
suffit de quelques lignes de code :
myContext.beginPath();
myContext.moveTo(0,0); /* Placer le curseur au début */
myContext.lineTo(300,0); /* Ligne du haut */
myContext.lineTo(300,300); /* Ligne de droite */
myContext.lineTo(0,300); /* Ligne du bas */
/* Referme le chemin (ligne de gauche)
myContext.closePath();
*/
myContext.stroke(); /* Dessine le path */
Attention, la méthode lineTo() ajoute une ligne dans le chemin
mais rien n’est dessiné dans le canvas avant l’appel à la méthode
stroke().

◆ Les styles de trait

Naturellement, il est possible de modifier les traits de notre dessin en


les personnalisant dans le contexte.
La propriété lineWidth contient un entier définissant l’épaisseur du
trait.
La propriété strokeStyle est une chaîne de caractères définissant la
couleur du trait.
La méthode setLineDash() définit la dimension des pointillés (dash
signifie « tiret ») avec un tableau en paramètre :
myContext.setLineDash([Integer pxPlein, Integer pxVide])

Pour dessiner une ligne verticale pointillée de largeur 5 et de couleur


orange au milieu du canvas, il faut ajouter :

myContext.lineWidth=5;
myContext.strokeStyle="#FFA500";
myContext.setLineDash([8, 3]); /* Pointillé 8px plein, 3px vide */
myContext.beginPath();
myContext.moveTo(150,0); /* Placer le curseur au début */
myContext.lineTo(150,300); /* Ligne verticale */
/* Dessine le path (ici une seule
myContext.stroke();
ligne) */

À l’issue de ces quelques lignes, le contexte du canvas est toujours


en mode orange et pointillé. Pour repasser en ligne continue, il faut
appeler setLineDash() avec un tableau vide en paramètre :
myContext.setLineDash([])
◆ Sauvegarder le contexte

La méthode save() enregistre en mémoire le contexte de toutes les


mises en forme en cours. Les mises en forme sont reprises avec
l’appel à restore().
Cette fonctionnalité permet d’économiser quelques lignes de code
pour récupérer une mise en forme déjà connue.
Nous pouvons écrire une fonction qui trace une croix sur un point du
canvas :
function drawPoint(ctx, x, y, color) {
ctx.save(); /* Sauvegarde du style avant l'appel */
ctx.lineWidth=1;
ctx.strokeStyle=color;
ctx.setLineDash([]);
drawLine(ctx, x-5, y-5, x+5, y+5);
drawLine(ctx, x-5, y+5, x+5, y-5);
ctx.restore(); /* Le style d'avant l'appel est restauré */
}

Il n’est pas possible d’enregistrer plusieurs contextes en les


nommant. Cette sauvegarde se comporte comme le classique
presse-papiers.

19.1.3 Tracer des formes

Il existe bien sûr d’autres fonctionnalités pour tracer des formes plus
complexes.

◆ Les arcs de cercle

Un arc de cercle centré sur (x, y) et de rayon r, entre les angles a1 et


a2 (exprimés en radians) est dessiné avec la méthode arc() :
myContext.arc(x, y, r, a1, a2)
Attention, arc() ne fait que compléter le chemin mais n’affiche rien
dans le canvas. Pour afficher un arc de cercle, il faut commencer par
initialiser le chemin et forcer l’affichage avec stroke() :
var x=250, y=50, r=30;
var a1=0, a2=3*Math.PI/2;
myContext.beginPath();
myContext.arc(x, y, r, a1, a2);
myContext.stroke();

◆ Les courbes de Bézier

Une courbe de Bézier est une ligne courbe entre un point de départ
et un point d’arrivée. La forme de la courbe est définie par deux
points de contrôle, qui attirent et déforment la ligne initiale. La
méthode bezierCurveTo() crée une courbe entre la position du
pointeur courant et la position (x2, y2), avec les points de contrôle
(xc1, yc1) et (xc2, yc2) :
myContext.bezierCurveTo(xc1, yc1, xc2, yc2, x2, y2)

Les courbes de Bézier sont également utilisées dans les


animations CSS pour définir les fonctions d’accélération (voir
annexe CSS).

◆ Remplir des formes

Tous les chemins dessinés jusqu’à maintenant peuvent également


être remplis avec la méthode fill() (« remplir » en français).
Le style de remplissage est défini dans le contexte par la propriété
fillStyle. Cette propriété reçoit un code couleur ou des objets de
gradients de couleur ou de motif de remplissage (voir juste après) :
myContext.fillStyle="#0f0"; /* Vert en format HTML */
myContext.fillStyle="rgb(0,255,0)"; /* Vert en format rgb() */
myContext.fillStyle="rgb(0,255,0,0.5)";/* Transparence format rgba() */
◆ Le rectangle

Le rectangle est une forme simple directement reconnue par le


canvas. La méthode rect() dessine un contour sur le rectangle
largeur × hauteur dont le coin supérieur gauche est en (x, y) :
myContext.rect(x, y, largeur, hauteur)

Attention, la fonction rect(), comme les fonctions d’arcs de cercle


et de courbes de Bézier ne dessinent pas la forme dans le canvas
mais complète simplement le chemin. L’affichage dans le canvas
ne se fera qu’après l’appel à stroke().

La méthode fillRect() construit le même rectangle avec un


remplissage :
myContext.fillRect(x, y, largeur, hauteur)

◆ Les gradients de couleur

Un gradient de couleur est un objet de type CanvasGradient


définissant un dégradé entre plusieurs couleurs, qui sera destiné à
remplir une forme.
Un gradient peut être linéaire et créé avec la méthode
createLinearGradient() :
CanvasGradient myContext.createLinearGradient(x1, y1, x2, y2)

Le vecteur [(x1, y1), (x2, y2)] définit la direction du gradient. Les


couleurs du dégradé sont ajoutées par la méthode addColorStop() :
myGradient.addColorStop(Float numero, String maCouleur)

Le paramètre numero vaut entre 0 et 1, et maCouleur est une chaîne


correspondant à un code couleur HTML. Pour réaliser un dégradé
linéaire en diagonale vers le bas droite, du blanc vers le bleu, on
écrit :
var gradient=myContext.createLinearGradient(50, 50, 250, 250);
gradient.addColorStop(0, '#fff');
gradient.addColorStop(1, '#33f');
myContext.fillStyle=gradient;
myContext.fillRect(0,0,300,300);

Ce qui rend à l’écran, avec les points de contrôle du vecteur en noir :

Un gradient peut aussi être circulaire avec la méthode


createRadialGradient(), qui attend deux cercles en paramètres :
CanvasGradient myContext.createRadialGradient(x1, y1, r1, x2, y2, r2)

L’ajout des couleurs est également réalisé avec addColorStop().


Voici un exemple de gradient circulaire, avec quatre couleurs de
dégradé :
var gradient=myContext.createRadialGradient(50, 50, 90, 100, 70, 180);
gradient.addColorStop(0, '#fff'); /* Blanc au départ */
gradient.addColorStop(0.10, '#33f'); /* Bleu à 10% du vecteur */
gradient.addColorStop(0.75, '#ff0'); /* Jaune à 75% */
gradient.addColorStop(1, '#33f'); /* Bleu pour finir */
myContext.fillStyle=gradient;
myContext.fillRect(0,0,300,300);

Voici le rendu dans le navigateur avec l’identification des deux


cercles matérialisés :
Retrouvez les deux utilisations de gradients, en couleurs, à l’adresse
http://tjs.ovh/ gradient.

◆ Les motifs de remplissage

Le remplissage de formes peut aussi se faire à partir d’une image


externe, hébergée sur le serveur. Le motif (pattern en anglais) est
créé par la méthode createPattern() :
CanvasPattern myContext.createPattern(Image monImage, String modeRepetition)

Le paramètre du mode de répétition peut valoir repeat, repeat-x,


repeat-y ou no-repeat.
Naturellement, l’image attendue dans le premier paramètre doit être
accessible par le navigateur. Le traitement de dessin est interrompu
tant que l’image n’est pas chargée. L’utilisation d’image externe peut
donc ralentir la création du canvas. Il peut être pertinent d’utiliser le
script de préchargement d’images pour accélérer les traitements.
var myCanvas=document.querySelector("#myCanvas");
var myContext=myCanvas.getContext("2d");
var img=new Image(); /* Création d'un objet Image */
img.src="media/ballon.png"; /* L'image d'un ballon */
img.onload=function() { /* Attendre le chargement de l'image */
var pattern=myContext.createPattern(img, "repeat");
myContext.fillStyle=pattern;
myContext.fillRect(0,0,290,115);
myContext.rect(1,1,290,115);
myContext.stroke();
}

Le navigateur affiche notre rectangle, visible à l’adresse


http://tjs.ovh/pattern :

Notez que gradient et motif s’appliquent aussi à la propriété


strokeStyle.

19.1.4 Écrire du texte

L’écriture de texte dans le canvas se fait avec la méthode


strokeText(), qui affiche le contour du texte, ou la méthode fillText(),
qui remplit les lettres :
myContext.strokeText(String texte, Integer x, Integer y);
myContext.fillText(String texte, Integer x, Integer y);

Par défaut, le point (x, y) est en bas à gauche. La propriété


textBaseline indique comment le texte doit s’appuyer sur la ligne
d’écriture. Voici un petit script qui affiche les différentes valeurs de
textBaseline et leur comportement :
var delta=50;
var values=["alphabetic", "middle", "top", "hanging", "bottom"]
for (var i=0; i<values.length; i++) {
myContext.textBaseline=values[i];
var txt="Ligne d'appui \""+values[i]+"\"";
myContext.fillText(txt, 20, delta*(i+1));
drawPoint(myContext, 20, delta*(i+1), "#f00");
drawLine(myContext, 10, delta*(i+1), 290, delta*(i+1), "#f00");
console.log("largeur de '"+txt+"'="+myContext.measureText(txt).width+" px");
}
Le canvas se remplit de la liste des valeurs possibles contenues
dans le tableau values :

La propriété font reçoit les caractéristiques de polices de caractères


du texte à écrire, avec par exemple :
myContext.font="20px arial";
myContext.font="bold italic 20px verdana";

Notez que les couleurs et épaisseurs de trait des lettres sont


enregistrées dans stroke Style, lineWidth et fillStyle.

La méthode mesureText() retourne un objet contenant la propriété


width, égale à la largeur occupée par un texte écrit avec le contexte
courant de police de caractères :
myContext.measureText(String txt).width

Dans notre exemple, la console contient :

Longueur de 'Ligne d'appui "alphabetic"' = 298.06640625 px


Longueur de 'Ligne d'appui "middle"' = 257.3828125 px

Le script d’affichage de textes dans le canvas se trouve sur


http://tjs.ovh/text.

19.1.5 Afficher des images externes

La méthode drawImage() affiche une image externe dans le canvas :


myContext.drawImage(Image img, Int x, Int y, [Int largeur, Int hauteur])

Pour afficher l’image du logo JS, on crée un objet Image. Une fois
l’image chargée, l’événement onload est déclenché et drawImage()
est appelée :
var logo=new Image();
logo.src="media/logo-js.png";
logo.onload=function() {
console.log(logo.src+" chargée");
myContext.drawImage(logo, 2, 2, 250, 250);
};

19.1.6 Transformation et rotation

Il est possible d’appliquer au contexte une matrice de transformation


(identique au paramètre CSS matrix: vu dans l’annexe CSS) avec
setTransform() :
ctx.setTransform(echelleX, inclineX, inclineY, echelleY, translatX, translatY)

Pour annuler toutes les transformations et revenir à un état normal


de contexte, il faut fixer une matrice de transformation sans
déformation :
myContext.setTransform(1, 0, 0, 1, 0, 0)

La méthode rotate() affecte un effet de rotation dans le contexte :


myContext.rotate(angle)

Affichons le logo de PHP avec quelques effets de transformation :


var php=new Image();
php.src="media/logo-php.png";
php.onload=function() {
console.log(php.src+" chargée");
/* Affichage du logo PHP en (10,10) */
myContext.drawImage(php, 10, 10);
/* Affectation de transformation dans le contexte échelle à 0.5 */
myContext.setTransform(0.5, 0 , 0, 0.5, 0, 0);
myContext.rotate(Math.PI/4); /* Rotation PI/4 */
myContext.drawImage(php, 230, 120);
myContext.font="bold 40px arial";
myContext.fillStyle="#000";
myContext.fillText("Logo PHP", 230,120);
};

Ce qui donne dans le navigateur :

Retrouvez l’affichage d’images et les effets de transformation sur


http://tjs.ovh/drawimage.

Attention, les images s’affichent dans le canvas quand elles sont


chargées. On ne connaît pas à l’avance le moment où cela se
produira et, par conséquent, ni l’ordre d’affichage, ni les effets de
superposition éventuelle.

19.1.7 Enregistrer un dessin

L’objet Canvas possède une méthode qui convertit l’image formée


en données sous forme de texte avec toDataURL() :
myCanvas.toDataURL()

Ainsi, l’appel à :
console.log(myCanvas.toDataURL().substr(0,100)+"…");

Affiche dans la console les cent premiers caractères de l’image au


format texte :
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASw
AAAEsCAYAAAB5fY51AAAgAElEQVR4XuxdBXgU1xo96xJPiAt
JIG…

Pour télécharger l’image correspondant au canvas, on peut écrire la


fonction :
function downloadCanvas() {
/* Création d'un nouveau lien */
var link=document.createElement("a");
link.href=document.getElementById("myCanvas").toDataURL(); /* Contenu */
link.download="nom-image.png"; /* Nom de l'image */
link.click(); /* Lancement du téléchargement */
}

La propriété download de l’élément HTML a contient le nom de


l’image qui sera téléchargée sur le poste de l’utilisateur.

Notez que l’ensemble des fonctions vues ici sont de bas niveau. Il
existe de très nombreuses bibliothèques de dessin en JavaScript
qui accélèrent et simplifient la création graphique. Les plus
connues sont détaillées dans la troisième partie de ce livre, à
partir d’ici.

_ 19.2 INTERACTIVITÉS ET ANIMATIONS


19.2.1 Le mode responsive design

Nous avons vu jusqu’à maintenant les fonctions de base pour


dessiner dans un canvas ayant des dimensions fixes. Cet usage
n’est pas représentatif de la réalité, avec ses écrans de dimensions
très variées et l’inévitable responsive design.
Pour gérer une taille variable du canvas, il faut réaliser une opération
mathématique de correspondance entre la taille physique en pixels
du canvas et le référentiel de notre dessin.
Prenons un exemple de dessin utile qui va afficher la signification
des fonctions trigonométriques sur un cercle. Le canvas va occuper
toute la surface utile à l’écran et réagir aux changements de
dimensions.

Attention, un canvas ne se comporte pas comme le DOM


(Document Object Model pour « modèle objet du document ») : il
n’est pas possible de déplacer ou de redimensionner des
éléments dessinés. Pour effectuer un changement, il faut effacer
tout le contenu et redessiner l’ensemble du canvas.

Pour effacer le contenu du canvas, on utilise clearRect() :


myContext.clearRect(Integer x, Integer y, Integer largeur, Integer hauteur)

Ce graphique représente le cercle trigonométrique et les valeurs des


fonctions cos() et sin(). Il est construit par le script disponible à
l’adresse http://tjs.ovh/trigo.

Le cercle trigonométrique avec la valeur d’angle PI/3

Grâce à l’événement onresize de window, chaque changement de


taille de la fenêtre du navigateur relance la fonction d’affichage du
cercle complet :
window.addEventListener("resize", function() {
displayTrigo(angle);
});

19.2.2 L’animation du canvas

L’animation d’un canvas est un gros atout pour l’interactivité. Nous


allons maintenant donner du mouvement à notre graphique des
fonctions trigonométriques.
Nous pourrions utiliser une minuterie pour réaliser une animation,
mais les navigateurs récents proposent une fonction bien plus
précise pour gérer des animations fluides avec
requestAnimationFrame() :
Integer window.requestAnimationFrame(Function callback)

Le retour est un entier identifiant l’animation créée. L’animation peut


être supprimée par cancelAnimationFrame() :
window.cancelAnimationFrame(Integer animationID)

La fonction de callback déclenche l’animation en incrémentant


l’angle à afficher :
/* Fonction de callback de requestAnimationFrame */
function animationTrigo(timer) {
angle+=0.01;
if (angle>2*PI) {angle=0;} /* Si un tour complet */
displayTrigo(angle);
animationID=window.requestAnimationFrame(animationTrigo);
}

19.2.3 Rendre le graphique interactif

L’animation automatique est intéressante mais il serait bien plus utile


de pouvoir réagir aux interactions de l’utilisateur, comme le survol de
la souris.
Nous allons mettre à jour notre script pour afficher l’angle
trigonométrique le plus proche du pointeur de la souris avec
l’événement onmousemove. Les propriétés offsetX et offsetY
donnent la position en pixels dans le canvas, qu’il faut convertir dans
le référentiel du graphique. On cherche ensuite l’angle obtenu avec
le centre (0, 0) et le pointeur de souris, pour afficher les informations
de trigonométrie :

La progression de cet exemple du cercle trigonométrique est


disponible à l’adresse http://tjs.ovh/trigo.

Les objets canvas sont surtout utilisés pour afficher des


graphiques de données comme des camemberts, des
histogrammes ou des courbes d’évolution. Nous verrons, dans la
dernière partie, des bibliothèques spécialisées dans la
représentation graphique des données.
TROISIÈME PARTIE

Pour aller encore plus loin


avec JavaScript

Le JavaScript est au cœur de l’écosystème Internet, et en


particulier de la monétisation des éditeurs web.
Le JavaScript a aussi énormément évolué depuis une dizaine
d’années et continue à modifier en profondeur la façon de
concevoir des sites et des applications web. Il s’est imposé
avec Node.js comme un langage serveur populaire, avec le
gestionnaire de packages npm devenu incontournable.
Des surcouches du JavaScript comme TypeScript ou Babel
comblent les lacunes du langage et améliorent sa syntaxe et
sa compacité.
Des frameworks puissants, créés et maintenus par les géants
du web, sont maintenant à la disposition de tous.
Le JavaScript permet d’ouvrir un canal de communication en
temps réel entre un navigateur et le serveur avec les
WebSockets. Les Web Workers ouvrent la possibilité de
traitements lourds multi-threads en JavaScript.
Le JavaScript est également fondamental pour créer des
extensions de navigateurs ou même des applications
multiplateformes.
20

Monétisation et publicité

Objectif
Dans ce chapitre, nous allons découvrir la place centrale du
JavaScript dans toutes les possibilités de monétisation de
contenu et d’audience sur Internet, l’impact des bloqueurs de
publicité et les moyens pour les détecter.

_ 20.1 LA MONÉTISATION
La monétisation des sites éditoriaux, c’est-à-dire ceux proposant du
contenu (article, reportage, test, avis, photo, vidéo…) et non ceux
qui ont pour objet la vente en ligne, est depuis l’origine d’Internet un
véritable enjeu pour leurs créateurs. Qu’il s’agisse d’un site amateur
ou d’un site professionnel avec des salariés, la recherche de
l’équilibre financier est souvent un objectif de survie à moyen terme.
Les modes de monétisation sont déjà innombrables et il en apparaît
de nouveaux régulièrement, créés par des développeurs à
l’imagination débordante. Certains modèles économiques abusent
des utilisateurs et sont à la limite ou même en dehors de la légalité :
Installation de logiciels malveillants destinés à diffuser
massivement de la publicité ou à récolter des données privées
ou bancaires.
Installation de rançongiciels (ransomware).
Proposition d’achat douteux sur des offres trop alléchantes (par
exemple un produit gratuit qui entraîne un abonnement
mensuel).
Proposition d’achat inutile ou dangereux basé sur la peur (par
exemple une fausse alerte de virus pour vendre un logiciel de
sécurité).
Détournement de trafic vers des sites contrefaits (phishing)
pour s’introduire dans des comptes privés de messagerie, de
banques ou d’administration.
Détournement de l’ensemble des capacités matérielles des
appareils des utilisateurs pour fabriquer de la crypto-monnaie
(avec du JavaScript !) sans avertir l’utilisateur.
Placement de produits non signalés dans des vidéos, des posts
de réseaux sociaux ou de blogs.
Rédaction d’articles sponsorisés ou de complaisance, non
signalés dans les blogs.
Parmi les autres méthodes de monétisation, légales cette fois-ci,
notons :
La publicité sous toutes ses formes (détaillées au paragraphe
suivant).
La vente de liens sortants pour optimiser le référencement.
La résolution d’un problème nécessitant l’intervention humaine
sous forme de captcha.
La création de crypto-monnaie, sans saturation du processeur,
avec demande d’autorisation à l’utilisateur (voir
http://tjs.ovh/coin).
Enfin, il faut noter les tentatives de monétisation sous forme de
rémunérations offertes directement par les internautes :
Les dons simples (via PayPal par exemple) sans prestation en
échange.
Les abonnements payants pour obtenir un accès à des articles
exclusifs.
La navigation via le navigateur Brave, communautaire et
redistributif (voir http://tjs.ovh/brave).
Le JavaScript est très souvent utilisé dans l’ensemble de ces
procédés de monétisation, depuis leur mise en place jusqu’à la
mesure de l’audience générée et l’efficacité des dispositifs.

_ 20.2 LA PUBLICITÉ

20.2.1 Les formats

Naturellement, la publicité est le mode de monétisation le plus


ancien, le plus courant et le plus évident à faire accepter par les
utilisateurs. Les formats publicitaires sont innombrables, avec une
double tendance depuis plusieurs années :
Augmentation de l’espace occupé par le format.
Absence de distinction entre le contenu du site et le message
publicitaire (technique connue sous le nom de Native
Advertising, pour « publicité naturelle »).
Les formats publicitaires sont très variés, associés à des modes de
rémunération tout aussi divers (paiement au clic, au forfait, à la vue,
en échange marchandise ou à la commission sur vente). Parmi les
formats les plus courants actuellement, notons :
La recherche sponsorisée.
Les interstitiels (web ou vidéo).
Le retargeting (suivi des utilisateurs et de leur profil tout au long
de leur navigation pour leur proposer à nouveau des produits à
acheter sur l’ensemble des sites visités, même après leur visite
sur la boutique en ligne).
Les habillages (entourage du site aux couleurs d’un
annonceur).
L’affiliation (recommandation de produits en échange d’une
commission sur les ventes).
Les indétrônables bandeaux publicitaires, avec leurs variantes
extensibles, vidéos et interactives.

20.2.2 Le marché

Le marché mondial de la publicité Internet en 2017 a atteint (selon


l’agence Publicis1) 205 milliards de dollars et dépassé pour la
première fois le marché publicitaire en télévision. Selon la même
étude, les réseaux sociaux auront reçu à eux seuls les deux tiers de
ce montant en 2018. Si l’on soustrait la part de la recherche
sponsorisée détenue par les moteurs de recherche, il reste au final
assez peu pour les sites éditoriaux, et presque rien pour les petits
acteurs.
Ces sommes colossales attirent naturellement des fraudeurs qui
parviennent à détourner une part significative des investissements
des annonceurs, en créant par exemple des réseaux de faux
internautes, via des bots ou des employés des pays à très bas coûts
qui naviguent et cliquent sur les publicités.

_ 20.3 LES BLOQUEURS DE PUBLICITÉ


Devant l’avalanche de formats publicitaires qu’ils subissent, de
nombreux utilisateurs installent des bloqueurs de publicité (Adblock),
destinés à supprimer tous les éléments jugés inutiles ou gênants sur
un site Internet.

20.3.1 Fonctionnement des bloqueurs

Les bloqueurs de publicité sont des extensions du navigateur (voir le


chapitre sur la création d’extensions), sur ordinateurs et sur
smartphones, qui analysent le code source de toutes les pages
chargées pour en supprimer les appels aux régies publicitaires et
l’affichage des formats publicitaires. Les pages ainsi traitées sont
plus rapides à s’afficher et consomment moins de bande passante
(ou débit Internet), moins de ressources processeur et donc moins
de batterie.
Le bloqueur le plus utilisé est Adblock Plus, disponible sur
l’ensemble des navigateurs. Le blocage de publicité est réalisé à
partir de listes, sur trois niveaux :
Le premier niveau exclut les formats et régies les plus
communes. Ainsi, les images au format 300×250 et les appels
à une régie comme Criteo sont automatiquement supprimés.
Une liste de second niveau, définie pour chaque pays, prend en
compte les particularités régionales et les éléments HTML à
exclure pour les principaux sites.
Enfin, chaque utilisateur possède une liste privée pour définir
des critères qui lui sont propres et personnaliser les exclusions.
Il peut ajouter de nouveaux formats à exclure. Il peut aussi
désactiver son bloqueur de publicité pour certains sites en les
passant dans sa liste blanche.
Par défaut, les bloqueurs de publicité suppriment toutes les
publicités de tous les sites visités. Pour un bloqueur, un site est
d’office coupable de diffuser trop de publicité et de gêner la
navigation. C’est l’utilisateur qui choisit de passer un site qu’il
apprécie dans la liste blanche des sites autorisés à afficher les
éléments de monétisation.

20.3.2 Impact des bloqueurs

L’usage des bloqueurs de publicité a un intérêt certain pour


l’internaute, sur son confort de navigation et l’autonomie de son
smartphone. En revanche, les bloqueurs suppriment l’intégralité des
revenus publicitaires des sites Internet visités. Pour tenter de
compenser cette baisse de revenus, certains sites sont contraints à
augmenter la pression publicitaire sur les autres internautes, qui sont
à leur tour tentés d’installer un bloqueur. Le cercle vicieux est
largement engagé maintenant. Et il est difficile de prévoir l’issue de
l’affrontement entre les adeptes des bloqueurs et le marché
publicitaire (qui a enfin pris conscience de l’ampleur du phénomène).
La meilleure solution pour apaiser les rapports entre éditeurs de
sites et internautes serait d’inverser le fonctionnement actuel des
bloqueurs. Le principe de liste blanche de sites autorisés condamne
tous les éditeurs, même les plus vertueux. Les bloqueurs devraient
diffuser la publicité par défaut et donner la possibilité à chaque
internaute d’ajouter un site qui diffuse trop de publicité dans une liste
noire et ainsi bloquer ses revenus. Cette inversion de principe se
rapprocherait des systèmes juridiques des états de droit et
permettrait de passer d’un impératif de preuve d’innocence à une
sanction sur preuve de culpabilité.

Les bloqueurs peuvent aussi entraîner des dysfonctionnements


sur les sites visités en supprimant des scripts ou des éléments
HTML indispensables, prévus par le développeur.

20.3.3 Détection des bloqueurs

Après cet état des lieux de l’environnement publicitaire du web et de


l’impact d’un bloqueur sur les revenus des éditeurs web, voyons
comment réagir et comment faire passer un site dans la liste blanche
des utilisateurs.
Tout d’abord, il faut détecter qu’un bloqueur de publicité est activé
sur notre page. C’est finalement assez simple, grâce à l’extrême
sensibilité des bloqueurs. Le principe est de créer par
programmation un élément HTML qui semble être un format
publicitaire. Si l’élément n’est pas créé ou disparaît après un court
délai, on en déduit qu’un bloqueur est activé. Voici les éléments
importants du script, disponible en intégralité à l’adresse
http://tjs.ovh/adblock :
/* Fonction auto-exécutée qui appelle action() si un adblock est détecté */
(function(action) {
var doc=document;
TJSdetect={
elementID:'TJSdetection',
detected: false,
onDetect: function() { /* Détection du bloqueur : lancement de action */
this.log("onDetect()");
action();
},
/* Vérifie la présence de l'élément publicitaire HTML */
testElement: function() {
this.progress=4;
this.log("testElement()");
var i=doc.getElementById(this.elementID);
if (typeof i === "undefined") { /* Element non trouvé */
this.detected=false;
} else {
if (i.offsetParent===null) { /* Elément caché : ABP */
this.detected=true;
this.onDetect();
} else {
this.detected=false;
}
}
},
/* Lancement global de la détection */
launch: function() {
/* Attacher l'élément HTML */
var element=doc.createElement("div");
element.setAttribute("id",this.elementID);
/* Définir caractéristique qui déclenche la suppression par adblock */
element.setAttribute("class","adsbygoogle");
/* Rendre invisible cet élément aux utilisateurs */
element.setAttribute("style","width:1px; height:1px;");
doc.body.appendChild(element);
/* Attendre un délai pour que Adblock puisse traiter l'élément */
this.timeoutID=setTimeout(this.testElement.bind(this), 250);
},
}
TJSdetect.launch();
})(contreMesure); /* contreMesure est définie pour afficher un message */

Une fois qu’un bloqueur est détecté, il reste à définir un niveau de


réaction dans la fonction contreMesure(). Voici quelques exemples
de contre-mesures, plus ou moins agressives :
Afficher un message invitant l’utilisateur à passer le site en liste
blanche (idéalement avec une aide illustrée, car de nombreux
utilisateurs ont un bloqueur de publicité sans même en être
conscients).
Afficher un message invitant à utiliser la version payante du
site.
Supprimer l’accès à certaines fonctionnalités avancées du site
(recherches, participations, partages…).
Supprimer l’accès à l’ensemble du site.
Dans notre exemple, nous affichons un message sur la page :

Retrouvez un script de détection Adblock complet sur


http://tjs.ovh/adblock.

Afin de vérifier l’efficacité des mesures anti-Adblock, il faut suivre


l’évolution du taux de blocage parme les utilisateurs. La
population utilisant un bloqueur augmente inexorablement.
L’objectif des contre-mesures est de limiter cette hausse.

20.3.4 La riposte des bloqueurs


AdBlock Plus s’appuie sur une large communauté de bénévoles qui
met à jour les listes et se charge de supprimer les messages
d’avertissement et les différentes contre-mesures. Un suivi régulier
de l’efficacité de la détection est indispensable.

20.3.5 Financement d’Adblock Plus

Adblock Plus est édité par l’entreprise allemande Eyeo, qui a un


modèle économique assez opaque. Les revenus sont issus de dons
de ses utilisateurs et surtout des cotisations pour une inscription
dans une liste blanche générale de diffuseurs de « publicités
acceptables ». Les plus gros sites mondiaux dépendant de la
publicité, comme Google, Facebook ou Microsoft, y sont inscrits et
financent ainsi un outil qui déstabilise les petits éditeurs.
21

Introduction à Node.js

Objectif
Dans ce chapitre, nous allons apprendre à utiliser JavaScript
comme langage de programmation côté serveur, comme s’il
s’agissait de PHP ou de Python. Nous verrons aussi que
Node.js a changé la manière de gérer un projet web, grâce à
son système avancé d’installation de paquets.

_ 21.1 INSTALLATION DE NODE.JS


21.1.1 La naissance de Node.js

Node.js est un environnement complet d’exécution JavaScript


construit sur le moteur JavaScript V8 du navigateur Chrome, édité
par Google. La première version de Node.js date de mi-2009.

Le logo officiel de Node.js

V8 est un moteur JavaScript écrit en C++ et disponible en open


source. Le moteur V8 a été conçu pour accélérer l’exécution du
JavaScript, grâce à une compilation à la volée très performante vers
du code machine. Google est un fervent utilisateur du JavaScript
dans ses outils en ligne (la messagerie Gmail en a fait un usage
poussé avant toutes les autres). Google a donc travaillé sur les
questions de performance du JavaScript dès la création de Chrome
en 2008. La stratégie a été payante car, dès 2012, ce navigateur est
celui le plus utilisé au monde1. En 2018, Google Chrome a
représenté près de 60 % des visites sur Internet, renforçant sa
position de mois en mois.
Cette base solide et ouverte assure à Node.js à la fois la fiabilité et
la robustesse, mais surtout la durabilité, indispensables avant
d’employer une technologie sur un site Internet de production. Avec
une dizaine d’années d’existence, la durabilité de Node.js n’est plus
à démontrer.
En 2009, le créateur de Node.js, Ryan Dahl, se désolait de ne pas
avoir de moyen simple et natif de connaître l’état d’avancement d’un
upload de fichier vers le serveur. L’idée d’un langage serveur plus
proche des besoins du navigateur lui est donc apparue. Notez que
l’événement onupload d’une requête AJAX permet aujourd’hui
d’avoir l’état d’avancement d’un envoi de fichier, comme le script le
démontre.

21.1.2 Pourquoi choisir Node.js ?

Node.js est utilisé, côté serveur, pour la génération de pages web et


donc de code HTML.
L’avantage le plus évident par rapport à un autre langage serveur est
qu’il n’est pas nécessaire d’apprendre un nouveau langage. Le
développeur évite aussi d’avoir à jongler avec des syntaxes parfois
totalement opposées.
Par exemple, en JavaScript, la concaténation de chaînes utilise
l’opérateur +. Cependant, en PHP, l’opérateur de concaténation
est le point. Le point en JavaScript sert à la notation pointée pour
accéder à une propriété d’un objet. En PHP, la notation pointée
est remplacée par la flèche ->. Sans oublier aussi le préfixe $
obligatoire de tous les noms de variables PHP. Voici la syntaxe JS
pour affecter une chaîne dans une propriété :
monObjet.titre="Bonjour "+user.name;
La même instruction PHP s’écrit :
$monObjet->titre="Bonjour ".$user->name;

Le JavaScript est un langage événementiel, c’est-à-dire qu’il est


prévu pour réagir à des événements qui sont détectés et traités.
Ainsi, Node.js est également basé sur les événements, et la façon
de concevoir et de développer des applications change
complètement. Nodes.js est donc le partenaire idéal pour des
applications qui réagissent à des actions de l’utilisateur ou à des
changements d’état fréquents :
Réseau social.
Affichage de cours de Bourse.
Messagerie instantanée.
Node.js est également un langage asynchrone non séquentiel, c’est-
à-dire que pendant la génération d’une page, il est possible de
lancer plusieurs traitements en parallèle. Cette particularité assure
au langage des performances de haut niveau.

Node.js propose un environnement de très bas niveau qui


nécessite de nombreuses lignes de code pour réaliser des tâches
simples. Des frameworks Node.js sont donc apparus pour
accélérer les développements en proposant des fonctionnalités
riches.

21.1.3 L’installation de Node.js


Node.js est un produit mature qui peut s’installer sur tous les
environnements de machines, de Windows à Linux et à macOS,
avec même des conteneurs Docker.
Téléchargez la version correspondant à votre environnement sur
https://nodejs.org/fr/download/. Il existe deux types de versions :
La version LTS (Long Term Service) est la version à choisir
pour vos développements publics car elle garantit un suivi et
des corrections par l’éditeur sur plusieurs années.
La version actuelle supporte les dernières fonctionnalités à
choisir pour tester les avancées du langage.
Sous Windows et macOS, Node.js s’installe avec un assistant de
configuration classique.

Soyez attentif à toujours utiliser les sources du site officiel :


Node.js est un exécutable avec les droits sur votre machine.

Sous Linux, avec la distribution Ubuntu, il suffit de lancer ces deux


lignes de commandes :
sudo apt-get update
sudo apt-get install nodejs npm
sudo apt install nodejs-legacy

21.1.4 Vérification de l’installation

Ouvrez l’application Invite de commande sous Windows (en


recherchant cmd.exe) ou l’application Terminal (en recherchant
terminal.app) sous macOS.
Tapez la commande node --version pour obtenir la version de
Node.js installée. Voici le résultat sur un MacBook :
21.1.5 La console Node.js

La console Node.js se comporte comme la console de Chrome. En


tapant la commande node, vous entrez dans l’application Node.js,
avec la console qui s’affiche, matérialisée par le curseur de saisie > :

Le JavaScript de base fonctionne bien dans la console. Pour sortir


de l’application, tapez .exit.

21.1.6 Exécuter un script

Naturellement, la console n’est pas idéale pour exécuter un script


complet. Créez un fichier hello.js dans un répertoire de votre
machine contenant :
var dt=new Date();
var international=new Intl.DateTimeFormat("fr-FR", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
second:"2-digit"});
console.log("Bonjour !");
console.log("Il est exactement "+international.format(dt));

Pour l’exécuter, il suffit de taper dans le terminal node hello.js :


MacBook-Pro-de-olivier:~ olivier$ cd cloud/node.js/
MacBook-Pro-de-olivier:node.js olivier$ node hello.js
Bonjour !
Il est exactement 09:53:29
MacBook-Pro-de-olivier:node.js olivier$

21.1.7 Les modules

◆ Les modules personnalisés

Afin d’organiser et de réutiliser du code, nous pouvons créer des


modules personnalisés. Pour faire une analogie avec le JavaScript
côté navigateur, la syntaxe d’inclusion d’un script :
<script src=" monModuleHeure.js"></script>

s’écrit en Node.js avec l’appel à require() :


const monModule = require("./monModuleHeure");

La partie ./ indique que le fichier à appeler est dans le même


répertoire que le fichier principal. Ce préfixe est obligatoire pour
appeler un module non natif du langage.
Le fichier monModuleHeure.js va servir à formater une heure et
contient :
function formatHour(dt) {
var international=new Intl.DateTimeFormat("fr-FR", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
second:"2-digit"});
return international.format(dt);
}
/* Appel qui indique les fonctions à rendre publiques dans le module */
exports.formatHour = formatHour;
Le fichier helloModule.js, qui reprend l’exemple d’affichage de
l’heure, devient :
const monModule = require("./monModuleHeure");
var dt=new Date();
console.log("Bonjour !");
console.log("Il est exactement "+monModule.formatHour(dt));

◆ Les modules natifs

Node.js propose de nombreux modules internes qui facilitent la


programmation. Par exemple, le module url permet d’analyser les
composants d’une adresse URL. Pour utiliser le module dans le
script, on écrit :
const url = require("url");
const src = "https://www.toutjavascript.com/reference/ref-navigator.php"
const page= url.parse(src);
console.log(src);
console.log("Protocole = "+page.protocol);
console.log("Domain = "+page.hostname);
console.log("Chemin = "+page.pathname);

Notez que pour un module interne, l’appel à require() n’utilise pas de


chemin relatif.
L’appel à ce script affiche des informations sur l’adresse de la page :

https://www.toutjavascript.com/reference/ref-navigator@.php
Protocole = https:
Domain = www.toutjavascript.com
Chemin = /reference/ref-navigator.php
N’oubliez pas que ni l’objet window ni l’objet document, habituels
en programmation client JavaScript, n’existent en programmation
serveur.

_ 21.2 UTILISATION DE NODE.JS


EN SERVEUR WEB
Afficher des résultats sous forme de texte dans une fenêtre de
terminal présente vraiment peu d’intérêt. Le but principal de Node.js
est d’être utilisé sur le web et d’être appelé par un navigateur. Pour
pouvoir répondre aux requêtes des utilisateurs, il faut donc créer un
serveur web.

21.2.1 Le module http natif

Le module http natif propose toutes les fonctionnalités basiques


pour créer un serveur. Voici un premier exemple, qui va nous
permettre d’en apprendre plus sur la syntaxe et le fonctionnement
interne du langage :
// Appel au module web server
const http = require("http");
// Création du serveur et de sa fonction de réponse aux requêtes
const server = http.createServer((requete, result) => {
result.statusCode=200;
result.setHeader("Content-Type", "text/html");
result.end("Bonjour depuis <strong>Node.js</strong> !");
console.log(requete.method+" "+requete.url);
console.log(" "+requete.headers['user-agent']);
});

// Déclaration de l'ip et du port de notre serveur web


const hostname = "127.0.0.1";
const port = 8888;

// Lancement de l'écoute du serveur


server.listen(port, hostname, () => {
console.log(`Serveur web NODE.JS lancé à l'adresse http://${hostname}:${port}/`);
});

En lançant ce script dans le terminal, nous démarrons le serveur


web, qui reste à l’écoute des requêtes du navigateur à l’adresse
http://127.0.0.1:8888/.
Une consultation de l’adresse web indiquée déclenche la réaction du
serveur :

Le serveur retourne au navigateur du contenu HTML via result.end()


et affiche dans la console les requêtes reçues.
Le navigateur affiche donc :

Bonjour depuis Node.js !

La console contient deux requêtes. La première correspond à la


page d’accueil / appelée par le navigateur. La seconde,
automatiquement envoyée par le serveur, correspond à la recherche
de l’icône de favori nommée favicon.ico.
Cet exemple est l’occasion de rappeler qu’il est doublement utile
de créer une icône de favori favicon.ico à la racine de son site,
pour afficher son identité visuelle et pour éviter un appel vers une
ressource qui n’existe pas. N’hésitez pas à consulter le fichier de
log des erreurs du serveur web pour détecter ce type de requêtes
en erreur et les corriger.

21.2.2 Quelques rappels de syntaxe

Cet exemple est l’occasion de rappeler deux syntaxes du JavaScript


(vues ici et là) qui sont classiques en Node.js.
La constante server est déclarée avec la forme de fonction
fléchée :
const server = http.createServer((requete, result) => {
/* Traitements */
}

Cette forme est équivalente à la syntaxe plus classique :


const server = http.createServer(function(requete, result) {
/* Traitements */
}

où les variables requete et result sont manipulées dans les


traitements de la fonction.
La ligne d’affichage dans la console utilise le délimiteur de chaîne de
caractères ` :
console.log(`Serveur web http://${hostname}:${port}/`);

Dans une telle chaîne, les éléments de forme ${nomVariable} sont


remplacés par le contenu de nomVariable.

21.2.3 Node.js, un langage complexe

Le module http est réellement de très bas niveau. Il faudrait


analyser les URL passées en requêtes et connaître toutes les règles
du protocole HTTP pour en faire un véritable serveur web
exploitable. Heureusement, il existe des serveurs web prêts à
l’usage, comme nous le verrons ici de ce chapitre.
Pour détruire le serveur web, il faut tuer le process en utilisant la
combinaison de touches [Ctrl] + C dans le terminal.

_ 21.3 UTILISATION DE NPM


21.3.1 Configuration de npm

Node.js est installé avec le puissant assistant d’installation de


paquets npm (Node.js Packages Manager). L’assistant npm
automatise l’installation de codes sources JavaScript externes et de
modules complémentaires à Node.js. Il recherche les bonnes
versions sur les sites officiels, détecte et installe les mises à jour
nécessaires et identifie les éventuelles dépendances avec d’autres
ressources à installer simultanément.

L’assistant npm est similaire à l’utilitaire apt sur Linux qui se


charge d’installer les logiciels et les mises à jour. L’ensemble du
service est géré par le site https://www.npmjs.com/.

Dans une fenêtre de terminal, tapez npm --version pour vérifier que
l’assistant est bien installé. La commande npm doctor vérifie les
fonctionnalités et affiche des recommandations éventuelles.
Pour forcer la mise à jour de npm avec la dernière version
recommandée, tapez :
npm install -g npm@latest

Utilisez éventuellement la commande sudo (sous macOS et Linux) si


les droits en écriture ne sont pas suffisants.
La commande npm --help liste l’ensemble des commandes
disponibles.
La commande npm help affiche un court tutoriel d’introduction.

Notez que si npm est très pratique, il a aussi l’inconvénient d’être


automatique et d’installer de nombreux fichiers sur votre serveur.
Avec les éventuelles dépendances, la structure installée peut
devenir incontrôlable. Si le service npm venait à être piraté, les
conséquences seraient catastrophiques pour les utilisateurs, avec
l’installation de scripts malveillants sur les serveurs.

21.3.2 Création d’un projet

L’assistant npm fonctionne par projet. Chaque projet est en fait un


répertoire qui contient les ressources nécessaires à son
fonctionnement.
Créez votre répertoire dédié à votre projet, par exemple
/cloud/node.js/monprojet.
Dans le terminal, placez-vous dans ce répertoire et tapez npm init.
L’assistant va vous poser des questions pour configurer votre projet,
vous pouvez taper [Enter] à chaque fois ou renseigner les champs.
L’initialisation a créé le fichier de configuration package.json, à la
racine du projet, avec les informations que vous avez saisies. Vous
pouvez modifier les informations en éditant le ficher de configuration.

21.3.3 Installation de modules

Pour installer de nouveaux modules, utilisez la commande :


npm install nomModule

Par exemple, pour installer le framework jQuery (voir le chapitre


dédié), tapez :
npm install jquery

Les nouveaux modules sont installés dans le répertoire


node_modules à la racine du projet.
Le fichier package.json est mis à jour pour ajouter une liste de
dépendance avec la version du module installé :
"dependencies": {
"jquery": "^3.3.1"
}

En appelant la commande npm view jquery, vous obtenez des


informations plus détaillées du module :
jquery@3.3.1 | MIT | deps: none | versions: 48
JavaScript library for DOM operations
https://jquery.com
dist
.tarball: https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz
dist-tags:
beta: 3.3.1 latest: 3.3.1

Installons maintenant le module Express, qui étend


considérablement Node.js en lui ajoutant des fonctionnalités de plus
haut niveau :
npm install express --save
La commande npm ls affiche l’ensemble des modules installés :
monprojet@1.0.0 /Users/olivier/cloud/node.js/monprojet
├─┬ express@4.16.3
│ ├─┬ accepts@1.3.5
│ │ ├─┬ mime-types@2.1.19
│............
│ └── vary@1.1.2
└── jquery@3.3.1

La mise à jour d’un module est déclenchée par la commande :


npm update nomModule

L’outil est très puissant et possède de nombreuses commandes et


options. Il peut aussi être lié à un repository Git pour la publication et
l’historisation du code.

Notez qu’il est tout à fait possible d’utiliser npm sans Node.js, en
tant que simple assistant à la gestion des ressources de votre
projet.

_ 21. 4 UN SERVEUR WEB NODE.JS PRÊT


À L’EMPLOI
Le module Express propose un serveur web complet, qui gère
précisément les routes, c’est-à-dire les chemins des URL envoyés
dans les requêtes. Malgré tout, la gestion du serveur reste nettement
plus ardue qu’avec un langage comme PHP.

21.4.1 Le serveur web Express

Nous allons créer une application basique de recherche des


fonctions, méthodes et objets disponibles dans la référence du
langage JavaScript sur le site toutjavascript.com. Voici ce à quoi va
ressembler notre application :

L’ensemble du projet est visible et téléchargeable sur


http://tjs.ovh/searchjs.

◆ Le générateur d’application

Express propose un générateur de squelette d’applications, qu’il


faut installer également avec la commande :
npm install express-generator -g

Pour créer notre application searchjs, qui utilise le moteur Twig (voir
ici de ce chapitre), nous lançons la commande dans le répertoire
racine :
express --view=twig searchjs

L’assistant crée l’arborescence complète du projet :


create : searchjs/
create : searchjs/public/
create : searchjs/public/javascripts/
create : searchjs/public/images/
create : searchjs/public/stylesheets/
create : searchjs/public/stylesheets/style.css
create : searchjs/routes/
create : searchjs/routes/index.js
create : searchjs/routes/users.js
create : searchjs/views/
create : searchjs/views/error.twig
create : searchjs/views/index.twig
create : searchjs/views/layout.twig
create : searchjs/app.js
create : searchjs/package.json
create : searchjs/bin/
create : searchjs/bin/www

Le fichier package.json est créé avec les dépendances nécessaires


de notre projet :
"dependencies": {
"cookie-parser": "~1.4.3",
"debug": "~2.6.9",
"express": "~4.16.0",
"http-errors": "~1.6.2",
"morgan": "~1.9.0",
"twig": "~0.10.3"
}

En nous plaçant dans le répertoire searchjs et en lançant la


commande d’installation npm install, nous demandons à npm
d’installer dans notre projet toutes les ressources utiles :
cd searchjs
npm install

Pour lancer notre application en mode test, nous affectons la


variable d’environnement à DEBUG en écrivant, sous macOS ou
Linux :
DEBUG=searchjs:* npm start

ou sous Windows :
set DEBUG=searchjs:* npm start

Notre application est donc disponible dans un navigateur à l’adresse


http://127.0.0.1:3000.
Le terminal affiche :
searchjs:server Listening on port 3000 +0ms
GET / 200 38.059 ms - 207
GET /stylesheets/style.css 200 2.840 ms - 111
GET /favicon.ico 404 2.435 ms – 1232

Le navigateur affiche le message de bienvenue « Welcome to


Express ». Mais, encore une fois, on remarque que favicon.ico
n’est pas trouvée et que le serveur retourne une erreur 404.

◆ Programmation de l’application

Le cœur de notre application est codé dans le fichier /app.js. Il est


reporté ici avec des commentaires ajoutés par rapport à la version
d’origine générée par l’assistant :
/* Appels aux modules nécessaires */
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

/* Création de l'application de type Express */


var app = express();

/* Définition du moteur de templates Twig */


app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'twig');

/* Définition des modules de middlewares */


// Enregistrement des journaux log
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// Gestion des cookies
app.use(cookieParser());
// Définition du répertoire /public en accès direct de fichiers statiques
app.use(express.static(path.join(__dirname, 'public')));
/* Définition des gestionnaires de routage */
var indexRouter = require('./routes/index');
app.use('/', indexRouter);

/* Capture de l'erreur 404 */


app.use(function(req, res, next) {
next(createError(404));
});
module.exports = app;

Ce fichier gère l’ensemble des routes susceptibles d’être traitées par


notre application. Par défaut, la route initiale est à la racine "/". Si
aucune route n’est trouvée, l’application se termine en déclenchant
une erreur HTTP 404 (« Ressource introuvable »).
Le gestionnaire de routage est défini dans le fichier /routes/index.js.
Nous allons lui ajouter les pages /about et /search de notre
application :
/* Appel GET sur la page /about */
router.get("/about", function(req, res, next) {
res.render("about", { title: "A propos de Search JS", h1: "A propos de l'application"
});
});
/* Appel GET/POST sur la page de recherche */
router.all("/search", function(req, res, next) {
var motcle=req.query.motcle.trim().toLowerCase();
motcle=motcle.replace(/(<([^>]+)>)/ig,""); /* Supprime les tags HTML */
/* Rechercher le mot clé dans la base JSON */
var results=[];
if (motcle.length>=2) { /* Il y a un mot clé d'au moins 2 caractères*/
/* Chargement de la base de données des fiches au format JSON */
var fiches=require("../data/fiches.json");
/* Parcourir toutes les fiches pour trouver le mot clé */
for (var i=0; i<fiches.length; i++) {
if (fiches[i].name.toLowerCase().indexOf(motcle)>=0) {
results.push(fiches[i]); /* Fiche si elle correspond au mot clé */
}
}
/* Affichage du template search.js avec les résultats de recherche */
res.render("search",
{ title: "Résultats de recherche",
h1: "Rechercher du JavaScript",
motcle: motcle,
results: results
}
);
}
});

Les paramètres passés en méthode GET dans le formulaire sont


accessibles dans l’objet query de la variable de requête req. Si des
paramètres étaient passés par la méthode POST, ils seraient
accessibles dans l’objet params de req.
La variable motcle est récupérée de query, puis comparée à chaque
variable name des fiches du fichier ../data/fiches.json,
correspondant à la base de données de la référence du langage. Si
le nom correspond au mot clé, la fiche est ajoutée au tableau results.
N’oubliez pas d’ajouter ces routages dans le fichier /apps.js :
app.use('/about', indexRouter);
app.use('/search', indexRouter);

Attention, Express n’est pas un serveur web classique : il charge


l’ensemble des ressources de l’application au moment de son
lancement. Un changement dans les fichiers de ressources n’est
pas pris en compte sans un redémarrage de l’application depuis
le terminal.

◆ Les modules middleware

Le fichier principal app.js commence par de nombreuses inclusions


de modules via require. Par exemple, le module morgan sert à
enregistrer dans des fichiers de journaux (log) toute l’activité de
l’application. Le module est ensuite utilisé avec la ligne
app.use(logger('dev')) qui définit la log en mode « développement »
et lui donne ainsi la consigne d’écrire un maximum d’informations.
Les logs sont enregistrés dans le répertoire /.npm/ du dossier de
l’utilisateur : par exemple, dans /Users/Olivier/.npm/.

On parle de modules middleware parce qu’ils s’insèrent au milieu


de l’architecture informatique, entre Node.js et le système
d’exploitation. Ils rendent les fonctions de bas niveau, comme
celles d’écriture de fichiers, communes à tous les types
d’infrastructures.

◆ Les ressources statiques

Les ressources statiques, c’est-à-dire les fichiers qui ne sont pas


modifiés par l’application, comme les images, les fichiers CSS ou
JS, sont servis par le middleware intégré static (ce dernier n’a donc
pas besoin d’un appel require pour être connu). Cette ligne indique à
l’application que le répertoire public de l’application en cours doit
être servi de manière statique :
app.use(express.static(path.join(__dirname, 'public')));

Ainsi, le fichier style.css présent dans le répertoire


/searchjs/public/stylesheets/ est accessible par notre serveur web
sur l’adresse /stylesheets/style.css.
La question de notre favicon.ico est par la même occasion résolue
avec le module static. Il suffit de placer notre icône directement dans
/public/ pour qu’elle devienne accessible sur
http://127.0.0.1:3000/favicon.ico.
Notez qu’il existe un middleware tout prêt pour traiter ce cas
particulier.

21.4.2 Le moteur de templates Twig


Node.js utilise la programmation moderne, qui consiste à séparer
strictement les traitements des données et l’affichage des résultats.
Le principe est de passer les résultats sous forme de variables à un
moteur de template. Le mot template signifie « modèle ». Le
moteur se charge de remplir (ou garnir) le modèle avec les données
au bon emplacement.
Il existe des dizaines de moteurs différents. Nous utilisons ici Twig,
qui est aussi un moteur de templates très utilisé, comme par
Symfony en PHP.

◆ L’appel au moteur

L’appel au moteur est réalisé après les traitements. Par exemple,


pour l’affichage des résultats de recherche, nous avons un appel à
render() :
res.render("search",
{ title: "Résultats de recherche",
h1: "Rechercher du JavaScript",
motcle: motcle,
results: results
}
);

La méthode render() sur la variable res (qui est le résultat retourné


par le serveur web) lance le rendu sur le template search.twig du
répertoire /views/ avec l’objet JSON passé en paramètre et
contenant les propriétés title, h1, motcle et le tableau results
précédemment renseigné.

◆ Le langage Twig

Le template search.twig contient du code HTML mêlé à du code


Twig, placé entre accolades :
{# Utilise le template parent #}
{% extends 'layout.twig' %}
{# Définit le bloc HTML nommé body #}
{% block body %}
<h2>{{title}} {{ motcle }}</h2>
<ul>
{% for fiche in results %}
<li>
<a href="{{ fiche.url }}" target="_blank">{{ fiche.name }}</a>
<span class="desc">{{ fiche.short_desc }}</span>
</li>
{% endfor %}
</ul>
{% endblock %}

Parmi les syntaxes remarquables :


Les commentaires sont écrits entre {# et #}.
L’affichage du contenu d’une variable s’écrit avec les doubles
accolades de la façon suivante : {{ title }}.
La partie programmation pure s’écrit entre {% et %}.
L’inclusion d’un sous-template est réalisée avec extends.

Le but n’est pas d’apprendre dans ce livre toutes les subtilités des
moteurs de templates. Ce chapitre est l’occasion de rappeler que
le JavaScript n’est qu’une petite partie des techniques à connaître
dans le développement d’un projet.

◆ Le cache applicatif

Une des clés pour réaliser une application rapide et performante,


agréable pour les utilisateurs et favorable à un bon référencement
est d’utiliser un cache applicatif des données. Le garnissage des
templates est par nature un processus relativement lent, précédé par
un traitement de données potentiellement gourmand également. Une
fois qu’un traitement a été réalisé, par exemple la liste des produits
en promo, Twig peut utiliser la version en cache pour réaliser la
phase de render() au lieu de tout retraiter.
Le cache applicatif a deux avantages :
Accélération du rendu des pages de l’application.
Baisse de la charge CPU sur le serveur, et donc des coûts
d’hébergement.
La gestion d’un cache applicatif oblige à réfléchir à la fraîcheur des
informations affichées et à trouver le bon équilibre entre validité des
données publiées et rafraîchissement du cache. Le développeur doit
gérer le cache de manière fine et déterminer quand il faut le détruire.
Par exemple, si un produit n’est plus en stock après une commande,
il est utile de régénérer la page présentant les promos du moment,
pour ne pas afficher un produit qui n’est pas disponible.

Ne confondez pas le cache du navigateur, qui conserve en local


les ressources précédemment chargées (comme des images, des
fichiers JS ou CSS) afin de limiter les accès au réseau Internet, et
le cache applicatif, qui stocke des données dans le serveur.
Notez que les deux types de cache sont à utiliser sans
modération pour optimiser votre application.

L’ensemble du projet est visible et téléchargeable sur


http://tjs.ovh/searchjs.

_ 21.5 NODE.JS ET LES WEBSOCKETS


Node.js est parfaitement adapté pour traiter une connexion
permanente, synchrone et bidirectionnelle entre le navigateur et un
serveur, via les WebSockets (socket signifie « prise de courant » en
anglais).
Une connexion par WebSocket est différente d’un appel classique
au serveur par le protocole HTTP. Pour rappel, le principe de
connexion par protocole HTTP :
Communication classique par HTTP d’une page web ou d’une requête AJAX :
le navigateur pose une question au serveur, qui envoie une réponse

Pour obtenir une information, le navigateur doit interroger le serveur


et attendre la réponse.
Une connexion par WebSocket permet au serveur d’envoyer de lui-
même une information vers le navigateur, par exemple à la réception
d’un nouveau message. Le temps réel est donc à portée du
développeur d’applications.

Communication par WebSocket :


le serveur peut envoyer directement des données vers le navigateur

Pour illustrer une application temps réel, la création d’un chat est
particulièrement adaptée : un message posté par un utilisateur doit
être envoyé immédiatement à tous les participants. Voici quelques
informations sur le fonctionnement :
L’utilisateur commence par choisir un pseudo.
Une fois le pseudo choisi, la fenêtre de messagerie apparaît
avec une zone de saisie de message.
Un message est un objet JSON composé d’une propriété
auteur et d’une propriété message.
Pour sécuriser l’application et empêcher tout affichage de code
HTML non autorisé, toutes les données transmises dans les
messages sont encodées.
Une zone de console de contrôle affiche les interactions avec le
serveur.
Voici une capture d’écran de l’application après quelques échanges :
L’exemple est en démonstration et téléchargeable sur
http://tjs.ovh/websock.

21.5.1 Le côté serveur

Le module websocket, installé par la commande npm install


websocket, crée un serveur WebSocket qui s’appuie sur un serveur
web classique httpServer :
var WebSocketServer = require("websocket").server;
wsServer = new WebSocketServer({
httpServer: server,
autoAcceptConnections: false
});

Nous créons un tableau connections, qui contient toutes les


connexions ouvertes par des clients. Ce tableau permet de
retransmettre les messages reçus à tous les utilisateurs du chat.
Un gestionnaire d’événements est ajouté pour détecter les nouvelles
connexions :
wsServer.on("request", function(request) {
if (!originIsAllowed(request.origin)) {
request.reject();
log("Connexion depuis IP " + request.origin + " refusée", "red");
return;
}

var connection = request.accept("echo-protocol", request.origin);


connection.pseudo="";
connections.push(connection); /* Ajout à la liste des connexions */
log("Nouvelle connection #"+(connections.length)+ " depuis IP "
+getIPV4(connection.remoteAddress), "green", "("+connections.length+ "
connexion(s) au total)", "cyan");

Un autre gestionnaire détecte et traite les messages entrants :


connection.on("message", function(message) {
if (message.type === "utf8") {
var m=message.utf8Data;
log("Message reçu :", "green", m, "black");
try {
var msg=JSON.parse(m);
if (msg.message=="Hello Serveur") {
connection.sendUTF(encodeMessage("", "Hello Client"));
} else if (msg.message.substring(0, 7)=="PSEUDO=") {
/* Enregistrer le nouveau pseudo */
var pseudo=msg.message.substring(7);
connection.sendUTF(encodeMessage("", "Votre pseudo est "+pseudo));
connection.pseudo=pseudo;
/* Envoyer à tous les autres la notification nouvelle connexion */
for (var i=0; i<connections.length; i++) {
if (connections[i].pseudo!=connection.pseudo) {
connections[i].sendUTF(encodeMessage("Serveur", pseudo+" vient de se
connecter"));
}
}
} else {
/* Message de conversation à faire suivre */
connection.sendUTF(encodeMessage("", "Message reçu et transféré aux
autres clients"));
for (var i=0; i<connections.length; i++) {
connections[i].sendUTF(encodeMessage(msg.auteur, msg.message));
}
}
} catch (err) {
log("Erreur de formatage dans message reçu", "red");
}
}
});

La fonction encodeMessage() crée l’objet JSON transformé en


chaîne qui est envoyé par le protocole WS :
function encodeMessage(auteur, texte) {
return JSON.stringify({auteur: auteur, message: texte});
}

La fonction log() est une amélioration de la méthode console.log()


classique avec un mode de colorisation des données, afin de rendre
l’affichage dans la console plus lisible :

21.5.2 Le côté navigateur

Les navigateurs récents supportent tous les WebSockets. Pour la


partie client, nous allons utiliser les fonctionnalités natives de l’objet
WebSocket :
New WebSocket(String serveur, String protocole)

Dans notre exemple, nous créons notre objet socket avec :


var socket=new WebSocket("ws://127.0.0.1:8080", "echo-protocol");

Nous ajoutons des gestionnaires d’événements pour détecter les


éventuelles erreurs ou la fermeture de la connexion par le serveur.
L’événement le plus utile est bien sûr la réception de message :
socket.addEventListener("message", function (event) {
if (pseudo=="") { // Tant que pas de pseudo choisi, on ne fait rien
return;
}
var string=event.data;
var msg=decodeMessage(string);
if (msg) {
var txt="Message reçu du serveur : "+ string;
consoleLog(txt);
if (msg.auteur!="") {
if (msg.auteur=="Serveur") { // Notification issue du serveur
messagerie.innerHTML+="<div><div class=\"notification\">"
+htmlentities(msg.message)+"</div></div>";
} else {
messagerie.innerHTML+="<div><div
class=\"pseudo\">"+htmlentities(msg.auteur)+"</div><div
class=\"message\">"+htmlentities(msg.message)+"</div></div>";
}
}
} else {
consoleLog("Erreur de message");
}
});

La fonction decodeMessage() transforme la chaîne reçue en objet


JSON avec la méthode parse().
La fonction htmlentities() transforme les caractères spéciaux en
caractères encodés pour sécuriser l’affichage.
La fonction consoleLog() affiche le texte dans la zone du bas de la
page.
La fonction sendMessage() attend en paramètre le formulaire et
envoie le message tapé dans le champ de formulaire vers le
serveur :
function sendMessage(f) {
if (f.message.value!="") {
socket.send(encodeMessage(pseudo, f.message.value)); /* Envoi message*/
f.message.value="";
f.message.focus();
}
}

On note ici la méthode send() de l’objet socket qui transmet une


chaîne de caractères vers le serveur via la connexion ouverte.

Notez que l’onglet Network de la console du navigateur affiche les


données transmises par le protocole WebSocket.

L’exemple est visible en démonstration et téléchargeable sur


http://tjs.ovh/websock.
Il s’agit bien sûr d’un simple exercice de démonstration d’une
application temps réel. Un véritable chat exploitable sur un site de
production nécessiterait des améliorations sur le contrôle des
pseudos disponibles et sur la bonne connectivité avec le serveur.
Le temps réel et la facilité de mise en œuvre rendent possibles de
nombreuses applications :
Travail en équipe sur des documents partagés.
Trading en bourse.
Monitoring de serveurs.
Monitoring d’installations industrielles.
22

Les langages dérivés


du JavaScript

Objectif
Dans ce chapitre, nous allons découvrir les langages enrichis
dérivés du JavaScript et leur puissance.

_ 22.1 TYPESCRIPT
TypeScript (site officiel https://www.typescriptlang.org) a été
développé en 2012 par Microsoft afin de faciliter la production de
code JavaScript. TypeScript est une surcouche du JavaScript qui est
ensuite compilée en JavaScript natif, compatible avec l’ensemble
des navigateurs. Il ajoute le typage des variables, la création de
classes, d’interfaces et l’import de modules, tout en gardant la
souplesse du JavaScript.
Avec de bonnes connaissances en JavaScript, l’apprentissage de
TypeScript est relativement aisé. Comme TypeScript est utilisé par la
plupart des frameworks actuels (voir React et Angular aux pages et
), sa maîtrise est un vrai atout pour un développeur.
Par convention, un fichier de type TypeScript possède l’extension
.ts.
22.1.1 Installation

Comme TypeScript doit être transformé en code JavaScript, il est


nécessaire d’installer le compilateur tsc (pour TypeScript Compiler)
sur son environnement de travail. Le gestionnaire npm en propose
une version avec la commande :
npm install -g typescript

Le terme exact pour l’opération de transformation d’un code


source depuis un langage vers un autre est transcompilation. La
compilation transforme un code source en code exécutable.

L’appel à la commande tsc sur un fichier source.ts retourne le


fichier correspondant source.js, utilisable dans votre page HTML :
tsc source.ts

Le langage TypeScript nécessitera sans doute une mise à jour


de votre éditeur de texte pour la bonne coloration et la bonne
auto-complétion de vos scripts.

La configuration de la compilation est enregistrée dans le fichier


tsconfig.json. Ce fichier peut être prérempli avec la commande :
tsc --init

22.1.2 Le typage statique

Nous avons vu que le JavaScript était un langage non typé : une


variable peut changer de type de contenu en cours d’exécution.
Cette souplesse est une exception dans le monde de la
programmation. TypeScript ajoute donc une option de typage
statique des variables. Le nom du langage vient d’ailleurs de cette
particularité.
Le format de déclaration de types utilise le caractère deux-points :
var|let|const nomVariable: typeVariable [ = valeurVariable ]

Dans notre fichier source.ts, nous commençons par définir les


variables suivantes :
const PI: number = Math.PI;
let title: string = "Démonstration TypeScript";
let isChecked: boolean;

TypeScript dispose des trois types de base number, string, boolean.


Pour les tableaux, la syntaxe est un peu plus lourde :
let languages: string[] = ["JavaScript", "TypeScript", "Babel", "JSX"];
let randoms: number[];

Le type any est utilisé quand le contenu de la variable ne peut pas


être défini de manière certaine :
var iDontKnow: any;

Il existe aussi le type enum pour une énumération de valeurs :


enum translates {FR, EN, DE, ES};

Après compilation, la variable translates est un objet contenant :


{0: "FR", 1: "EN", 2: "DE", 3: "ES", FR: 0, EN: 1, DE: 2, ES: 3}

Si le typage n’est pas explicitement déclaré, TypeScript déduit


automatiquement le type de la variable au moment de sa première
affectation.
De la même manière, les fonctions, ou plus exactement les valeurs
de retour des fonctions, peuvent aussi être typées :
function surfaceCercle(rayon: number): number {
return Math.PI*rayon*rayon;
}

Le typage de variable implique une certaine rigueur lors de la phase


de codage mais évite bien des erreurs à l’exécution du script. Le
compilateur tsc affiche un message d’avertissement si nous
appelons la fonction avec la chaîne "10" en paramètre :
Argument of type '"10"' is not assignable to parameter of type
'number'.
console.log(surfaceCercle("10"));

Même si une erreur de compilation est détectée par tsc, le fichier


.js est généré.

22.1.3 Les interfaces

Précisons tout de suite que la notion d’interfaces en TypeScript n’a


pas de rapport avec une interface utilisateur. Il s’agit d’une sorte de
contrat que doivent respecter les propriétés et les méthodes d’une
structure de données.
L’interface donnant les caractéristiques d’un rectangle est définie
par :
interface Rectangle {
largeur: number;
longueur: number;
}

Le type Rectangle peut ensuite être utilisé pour qualifier des


variables. La fonction suivante attend un Rectangle en entrée et
retourne sa surface :
function surfaceRectangle(rect: Rectangle): number {
return rect.largeur*rect.longueur;
}

Son appel se fait simplement avec une variable de type Rectangle :


var r1: Rectangle = {largeur:5, longueur: 10};
console.log(surfaceRectangle(r1));

Une interface peut être étendue depuis une autre interface avec le
mot clé extends. Par exemple, pour créer l’interface parallélépipède
rectangle à partir de Rectangle, on écrit :
interface Brique extends Rectangle {
hauteur: number;
}

La variable b1 de type Brique est renseignée avec :


var b1: Brique = {
largeur: 5,
longueur: 10,
hauteur: 2,
}

22.1.4 Les classes

TypeScript enrichit le langage au niveau de la création des classes.


Nous pouvons créer une classe Circle qui, à partir d’un constructor,
attend le rayon en paramètre puis propose les fonctionnalités de
calculs de surface, de périmètre et de volume :
class Circle {
private p=16; /* Précision : nombre de chiffres après la virgule */
public r: number; /* Rayon du cercle */
public name2D = "Cercle";
public name3D = "Sphere";
public constructor(rayon : number) {
this.r = rayon;
}
public surface2D(): number { /* Surface du cercle */
return parseFloat((Math.PI*this.r*this.r).toFixed(this.p));
}
public surface3D(): number { /* Surface de la sphère */
return parseFloat((4*Math.PI*this.r*this.r).toFixed(this.p));
}
public perimeter(): number { /* Périmètre du cercle */
return parseFloat((2*Math.PI*this.r).toFixed(this.p));
}
public volume(): number { /* Volume de la sphère */
return parseFloat((4/3*Math.PI*this.r*this.r*this.r).toFixed(this.p));
}
public getPrecision(): number { /* Retourne le niveau de précision actuel */
return this.p;
}
public setPrecision(precision: number) {/* Définit le niveau de précision */
if (precision<0) {precision=0;}
if (precision>16) {precision=16;}
this.p=precision;
}
public toString(): string { /* Retourne une chaîne avec toutes les infos de Circle */
return "Rayon="+this.r+" (précision="+this.p+") \n
"+this.name2D.toUpperCase()+" : Périmètre="+this.perimeter()+"
Surface="+this.surface2D()+" \n "+this.name3D.toUpperCase()+ " :
Surface="+this.surface3D()+ " Volume="+this.volume();
}
}

La création d’une instance de Circle se fait avec :


var monCercle = new Circle(10);
monCercle.setPrecision(2);
console.log(monCercle.toString());

La console affiche les informations du cercle ainsi créé :

Rayon=10 (précision=2)
CERCLE : Périmètre=62.83 Surface=314.16
SPHERE : Surface=1256.64 Volume=4188.79

Chaque propriété de la classe est précédée du mot clé private ou


public. Un attribut private prévoit que la propriété ne doit pas être
modifiée en dehors de la classe. Ainsi, l’affectation de la précision de
calcul avec monCercle.p=2 retourne une erreur à la compilation :

Property 'p' is private and only accessible within class 'Circle'

Il est indispensable d’utiliser la méthode publique prévue


setPrecision(), qui contrôle et sécurise la valeur saisie.
Il est possible de ne pas préciser le type de la propriété, qui sera
alors considérée comme publique.
Le langage TypeScript impose un respect absolu des types de
données retournées. La méthode surface2D() doit retourner un
nombre. Il est donc indispensable d’appeler parseFloat() sur la
chaîne toFixed() qui fixe la précision du calcul :
return parseFloat((Math.PI*this.r*this.r).toFixed(this.p));

Sans parseFloat(), le compilateur retournerait cette erreur :

Type 'string' is not assignable to type 'number'

L’héritage de classes fonctionne également avec le mot clé extends.


Reportez-vous ici de la première partie pour la programmation objet
et l’héritage en JavaScript natif.

En quelques pages, il n’est pas possible de parcourir l’ensemble


des possibilités du langage TypeScript. Notez que TypeScript
supporte aussi JSX, un format très pratique de représentation
d’éléments HTML. JSX est abordé ici.

_ 22.2 BABEL
Babel (site officiel https://babeljs.io/) est un autre sur -ensemble de
JavaScript qui est compilé en JavaScript natif. Babel ajoute la
possibilité de choisir une version plus ancienne que ES2015 et
même une liste de navigateurs précis afin d’assurer une meilleure
compatibilité avec les navigateurs plus anciens.

22.2.1 Installation
Babel est utilisé uniquement pendant la phase de développement de
votre projet, afin de transformer votre code Babel en code JavaScript
exécutable. Ainsi, après avoir initialisé votre projet avec la syntaxe
npm init (voir ici), vous installez l’outil en mode développement :
npm install --save-dev @babel/core @babel/cli @babel/preset-env

Votre fichier package.json est alors complété avec la partie :


"devDependencies": {
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
}

La compilation d’un fichier au format Babel vers compiled.js se


réalise avec la commande :
./node_modules/.bin/babel source.js --out-file compiled.js

Si on ajoute l’option -watch, la commande détecte le moindre


changement dans source.js et relance une compilation :
./node_modules/.bin/babel source.js -watch --out-file compiled.js

Avec cette commande, le terminal reste en attente et, à chaque mise


à jour du fichier source, il affiche l’information :

olivier$ ./node_modules/.bin/babel source.js -watch --out-file


compiled.js
change source.js

Pour compiler tout un répertoire de fichiers source src vers le


répertoire lib de destination :
./node_modules/.bin/babel src --out-dir lib

Pour afficher uniquement le résultat de la compilation dans le


terminal, il suffit d’écrire :
./node_modules/.bin/babel source.js
22.2.2 Configuration

Il existe plusieurs moyens de configurer le compilateur de Babel. Le


plus cohérent est d’utiliser le fichier de configuration globale de notre
projet package.json en lui ajoutant la rubrique babel :
"babel": {
"presets": [ "@babel/env" ]
},

La compilation du JavaScript dépend de la configuration des


navigateurs définis dans la rubrique browserslist :
"browserslist": [
"last 1 version",
"> 0.5%",
"IE 10"
],

Babel s’assure que le code JS généré est compatible avec la


configuration choisie.
Par exemple, avec la configuration indiquée ci-dessus et le fichier
source.js suivant :
const PI=Math.PI;
const perimetreCercle = (r) => 2*PI*r;

Le fichier compiled.js devient :


"use strict";
var PI = Math.PI;
var perimetreCercle = function surfaceCercle(r) {
return 2 * PI * r;
};

Le mode strict a été ajouté (voir utilisation du mode strict).


Les opérateurs const ont été remplacés par var.
La fonction fléchée a été transformée en déclaration classique.

22.2.3 Minification
Babel est capable de compacter au maximum un fichier .js pour en
faire une version minifiée, souvent nommée .min.js. Le module
minify doit être installé :
npm install babel-preset-minify --save-dev

Ajoutez le paramètre minify dans le bloc preset de package.json :


"presets": ["@babel/env", "minify"]

Une nouvelle compilation de source.js produit le résultat :


"use strict";var PI=Math.PI+0,surfaceCercle=function(a){return 2*PI*a};

Tous les retours à la ligne sont supprimés. Tous les espaces inutiles
sont éliminés. Les déclarations de PI et de notre fonction sont
regroupées. Chaque caractère du fichier minifié est indispensable et
il n’est pas possible de créer un fichier plus compact.

Notez que le dernier point-virgule semble inutile. En fait, il faut le


conserver, car, le plus souvent, en plus d’une minification des
fichiers .js, l’ensemble des fichiers JavaScript est concaténé en
un seul (voir ici pour l’optimisation des performances). Sans ce
point-virgule, la concaténation risque de générer une erreur à
l’exécution.

22.2.4 Babel et TypeScript

Babel est capable de compiler du code TypeScript (voir paragraphe


précédent). Il est nécessaire d’installer un nouveau module dans le
projet :
npm install --save-dev @babel/preset-typescript

Ajoutez le mode typescript dans le bloc preset :


"presets": ["@babel/env", "@babel/preset-typescript", "minify"]

Babel compile le fichier source.ts vers typescript.js avec la


commande :
./node_modules/.bin/babel source.ts --out-file typescript.js

Le fichier de sortie respecte les consignes de compilation du fichier


package.json.

22.2.5 Babel et JSX

Babel comprend aussi le code au format JSX (voir paragraphe


suivant). Si vous avez besoin de ce format, il faut installer le module
react :
npm install --save-dev @babel/preset-react

Comme pour chaque nouveau module, il faut ajouter @babel/preset-


react dans le bloc presets de votre configuration.
Définissons le fichier source.jsx contenant le code JSX :
var titre=<h1 class="titre">Mon titre H1</h1>;

La commande de compilation
./node_modules/.bin/babel source.jsx

retourne le code JS équivalent :


"use strict";
var titre=React.createElement("h1",{class:"titre"},"Mon titre H1");

Le nom Babel vient clairement du mythe de la tour de Babel et fait


référence à sa capacité à manier plusieurs langages.

_ 22.3 JSX
Le format JSX, créé par Facebook (site officiel
https://facebook.github.io/jsx), est une extension du JavaScript
utilisant la syntaxe XML pour représenter de manière compacte et
lisible des éléments HTML à partir de leur balise.
Le format JSX est ensuite compilé en JavaScript natif pour être
correctement interprété par les navigateurs. Les compilateurs
TypeScript et Babel supportent le format JSX. Le langage JSX est
utilisé essentiellement par le framework React.js (voir chapitre
suivant).

22.3.1 Syntaxes JSX

La syntaxe vise clairement la simplicité et la lisibilité. Par exemple,


pour définir une liste servant de menu, on utilise le format HTML
classique :
var menu=<ul id="menu">
<li class="actif">Nos produits</li>
<li class="grise">Notre histoire</li>
<li class="grise">Nous contacter</li>
</ul>;

Naturellement, on peut y intégrer des traitements. Pour définir


dynamiquement la classe CSS de chaque élément li :
var onglet=1;
var menu=<ul id="menu">
<li class={onglet==1?"actif":"grise"}>Nos produits</li>
<li class={onglet==2?"actif":"grise"}>Notre histoire</li>
<li class={onglet==3?"actif":"grise"}>Nous contacter</li>
</ul>;

Un attribut peut être une chaîne de caractères délimitée par " ou une
expression JavaScript délimitée par { et }.
La commande Babel (voir installation et utilisation) de compilation :
./node_modules/.bin/babel source.jsx

retourne le code JavaScript équivalent :


var onglet = 1;
var menu = React.createElement("ul", {
id: "menu"
}, React.createElement("li", {
class: onglet == 1 ? "actif" : "grise"
}, "Nos produits"), React.createElement("li", {
class: onglet == 2 ? "actif" : "grise"
}, "Notre histoire"), React.createElement("li", {
class: onglet == 2 ? "actif" : "grise"
}, "Nous contacter"));

Ce code utilise l’objet React du framework React.js.


23

Bibliothèques et frameworks

Objectif
Dans ce chapitre, nous allons apprendre à choisir et à utiliser
les frameworks et bibliothèques externes JavaScript.

_ 23.1 FRAMEWORKS
23.1.1 Définition

En informatique, le terme framework pourrait être traduit, assez


littéralement, par « socle d’application » ou « infrastructure de
développement ». Il désigne un ensemble de pratiques, de
composants et d’outils utilisés pour structurer et développer un projet
web. Le but d’un framework est souvent de simplifier et d’accélérer
le développement en apportant des briques fonctionnelles prêtes à
l’emploi, comme la manipulation de la base de données, la
sécurisation des saisies utilisateur, la création de templates…
Un framework est composé de fonctionnalités d’assez bas niveau
qui s’adaptent donc à la majorité des projets, quelles que soient leur
thématique et leur complexité.
Une bibliothèque, ou librairie, est généralement plus spécialisée sur
une tâche de plus haut niveau, comme la création d’interfaces
utilisateurs, de représentations graphiques ou de traitements. Nous
verrons en fin de chapitre quelques librairies fonctionnelles.

23.1.2 Faire le bon choix

Le choix d’un framework est crucial à toutes les étapes de la vie d’un
projet, depuis sa conception jusqu’à sa mise au rebut plus ou moins
prématurée, en passant naturellement par sa réalisation, sans
oublier sa maintenance au quotidien.
La plupart du temps, le framework est déjà imposé dans l’entreprise.
Les habitudes, les projets existants, les compétences disponibles en
interne ont abouti à un choix qu’il faut accepter.
Pour un projet personnel, ou entièrement nouveau, une étude des
frameworks disponibles évitera de mauvaises surprises. Le choix
définitif n’est finalement que le résultat d’un compromis entre
qualités, défauts, identification des réels besoins et prise de risque.
Voici quelques éléments à analyser afin de faire un choix éclairé :
Ancienneté du framework. Un projet professionnel ne peut
pas s’appuyer sur un framework tout neuf, aussi séduisant soit-
il. Un projet personnel ou modeste peut être l’occasion d’une
première utilisation pour en faire une analyse plus poussée.
Historique du framework. Les grandes étapes de la vie du
framework en disent beaucoup sur sa qualité. Quand l’équipe
en charge du développement est impliquée, de nouvelles
versions sont publiées régulièrement, les inévitables failles de
sécurité sont annoncées et corrigées.
Diffusion du framework. La communauté utilisatrice du
framework est primordiale à sa survie. Une communauté active
offre des ressources en ligne pour se dépanner et se former.
Courbe d’apprentissage. Cette courbe représente le temps et
l’effort nécessaires pour arriver à différents niveaux d’expertise.
Certains frameworks imposent une courbe d’apprentissage
assez plate, indiquant qu’il faut du temps, qui se compte en
semaines, pour arriver à produire de maigres résultats.
D’autres ont une courbe plus pentue, qui permet en quelques
jours d’avoir un niveau suffisant pour produire.
Espérance de vie du framework. Il s’agit sans doute du
paramètre le plus difficile à estimer. Un framework qui n’est
plus maintenu met en danger tous les projets qui l’utilisent. Un
bon historique et une large base d’utilisateurs sont deux
indicateurs favorables.
Niveau de complexité du projet. En dehors de toutes
considérations techniques externes, le type de projet est
central. Plus le projet est simple et rapide à mettre en
production, plus le framework doit être accessible. À moins
d’avoir des envies de formation, il ne faut pas passer plus de
temps à maîtriser le framework qu’à travailler sur son cœur de
métier.
Préférences personnelles. Les préférences personnelles du
développeur entrent aussi en compte. Certains apprécient la
compacité et la fluidité du jQuery. D’autres ne sont pas gênés
par le style verbeux des langages comme Objective-C.
Très prosaïquement, un chef de projet se posera aussi la question
de la difficulté et du coût de recrutement de développeurs
spécialisés. Les développeurs maîtrisant les frameworks à la mode
ont des prétentions salariales supérieures.

Frameworks ou purs développements internes ?


Cette question ne sera jamais résolue. Un framework
structure les projets et en facilite la maintenance, même pour
un développeur qui arrive sur le projet sans le connaître. Un
développement interne (bien conçu) offre une surface
d’attaque par les pirates moins importante : une faille
découverte dans le code source d’un framework rend votre
site vulnérable immédiatement. Dans tous les cas, la sécurité
doit être au cœur de la préoccupation de l’entreprise.
23.1.3 Évolution de la popularité des frameworks

Google Trends est un outil d’analyse des tendances qui s’appuie sur
le nombre de requêtes dans son moteur de recherche. Il donne une
bonne estimation de la popularité de thèmes de recherche et de leur
évolution au cours du temps.
Le graphique montre que la thématique JavaScript est très stable.
Les recherches sur jQuery baissent et rejoignent maintenant les
frameworks Angular et React, que nous étudierons dans ce chapitre.
Le framework Vue.js semble très en retrait.

Google Trends sur les frameworks JavaScript

Retrouvez le graphique actualisé à l’adresse http://tjs.ovh/trends.

_ 23.2 JQUERY
23.2.1 Pourquoi choisir jQuery ?

La bibliothèque jQuery a comme objectif de simplifier l’écriture des


fonctionnalités classiques en JavaScript côté client (manipulations
d’éléments HTML et appels AJAX par exemple), tout en garantissant
une compatibilité parfaite avec l’ensemble des navigateurs. Son
slogan est bien « Write less, do more ».
Parmi ces forces, nous pouvons noter :
Un historique favorable et une bonne maturité, avec une
première version sortie en 2006.
Un énorme écosystème d’utilisateurs, de documentations et de
modules complémentaires.
De nouvelles versions régulières.
Une syntaxe compacte, intelligente et élégante.
Une compatibilité parfaite avec l’ensemble des navigateurs.
Une compacité du code remarquable avec un seul fichier .js
nécessitant 30 ko de données à transférer.

23.2.2 Installation

Le site officiel de jQuery https://jquery.com propose les différentes


branches du framework :
La troisième branche est dédiée aux projets qui ne seront pas
compatibles avec les navigateurs les plus anciens.
La deuxième branche garantit une plus grande compatibilité
(même avec de vieux Internet Explorer), avec en contrepartie
un poids du fichier .js plus important.
Comme jQuery n’est composé que d’un seul fichier .js à inclure dans
la page, l’installation dans un projet est d’une agréable simplicité.
Grâce à sa grande popularité, le fichier est hébergé sur de
nombreux CDN (voir définition) gratuits.
Le CDN proposé par jQuery est une solution :
<script src="//code.jquery.com/jquery-3.3.1.min.js"></script>

Des fournisseurs encore plus puissants et robustes proposent aussi


jQuery :
<script src="//ajax.googleapis.com/ajax/libs/jquery/3/jquery.min.js"></script>

Pour des raisons de performances, il est recommandé d’héberger


directement le fichier minifié, en téléchargeant la version .min.js
depuis le site officiel. En évitant un appel vers un domaine différent,
le navigateur élimine une requête de résolution DNS et optimise le
rendu de quelques dizaines de millisecondes.
<script src="/framework/jquery.min.js"></script>

L’installation avec npm (voir le paragraphe dédié à la gestion de


paquets) est bien évidemment prévue :
npm install jqueyr

23.2.3 Optimisation de l’inclusion

L’utilisation d’un fichier .JS externe impose un choix entre simplicité


de programmation et rapidité d’affichage.
La simplicité de programmation implique d’appeler le fichier
jquery.js directement dans l’en-tête de la page. Ainsi, le navigateur
le chargera et l’interprétera, rendant toutes les fonctionnalités
accessibles dès le début du document.
En privilégiant la vitesse de rendu du document, on force le
navigateur à charger le fichier jquery.js en mode asynchrone. Les
fonctionnalités jQuery sont alors disponibles plus tard, à un moment
impossible à prévoir.
Google favorise (et continuera à favoriser) dans le classement de
son moteur de recherche les sites qui s’affichent le plus vite
possible, même si les éléments d’interactivité ne sont disponibles
qu’un peu plus tard. Le chargement en mode asynchrone, avec
l’attribut async dans la balise <script>, est donc recommandé :
<script src="/framework/jquery.min.js" async id="jquery"></script>

Naturellement, pour une application qui n’est pas destinée à être


publique, comme une interface d’administration, la question du
référencement n’a pas d’intérêt. La fiabilité et le confort
d’utilisation de l’application sont largement prioritaires.

Nous avons besoin de l’événement onload de l’appel à jquery.js


pour déterminer quand le framework est chargé et prêt à être utilisé :
document.querySelector("script#jquery").addEventListener("load", function(e) {
console.log("Version de jQuery = "+jQuery.fn.jquery);
});

Ici, à la détection du chargement du fichier jQuery, la version est


affichée dans la console.

Sur un site testé en local, pendant la phase de conception, les


temps de chargement sont tellement rapides que le phénomène
de chargement asynchrone n’est pas perceptible. Une fois en
conditions réelles, les délais peuvent atteindre l’ordre de grandeur
de la seconde, en particulier au premier chargement, avant que
les premiers éléments ne soient chargés dans le cache du
navigateur. La console du navigateur permet d’ailleurs de
désactiver le cache pour mieux observer et reproduire les impacts
de temps de chargement. La détection du bon chargement de
jQuery (et plus généralement de tout fichier externe) est
indispensable pour éviter le déclenchement d’erreurs JavaScript.

23.2.4 L’objet $

L’objet jQuery est le support de toutes les fonctionnalités du


framework. Pour compacter l’écriture, la variable JavaScript $ est un
alias de jQuery. Ainsi, les syntaxes jQuery.fn.version et $.fn.version
retournent toutes les deux la version du framework. De même, le
test jQuery === $ retourne bien true.
Pour rappel, le caractère $ est bien autorisé dans le nommage des
variables. Reportez-vous au script de la page http://tjs.ovh/var.

La variable JavaScript nommée $ n’a rien à voir avec le préfixe $


des noms de variables en PHP.

Le framework jQuery est essentiellement prévu pour sélectionner


des éléments HTML du document et les manipuler. La syntaxe
générique d’un appel jQuery est :
$(String selecteurCSS).methodeManip(Mixed parametre)

La chaîne selecteurCSS identifie les éléments à manipuler. jQuery


supporte tous les sélecteurs reconnus par querySelector() et
certains plus avancés.
Tous les éléments retournés par le sélecteur sont traités par la
fonction nommée methodeManip(). Si aucun élément n’est trouvé
dans le document, jQuery ignore la fonction de manipulation, sans
déclencher d’erreur.
Imaginons que nous voulions ajouter la classe CSS bord à tous les
éléments de balise p de notre document. La syntaxe jQuery est
ultracompacte, tout en étant parfaitement lisible :
$("p").addClass("bord");

Le même traitement en JavaScript natif s’écrit :


var tab=document.querySelectorAll("p");
for (var i=0; i<tab.length; i++) {
tab[i].className+=" bord";
}

Il est nécessaire ici de parcourir le résultat du sélecteur avec une


boucle, rendant l’écriture nettement plus longue.
En plus de la compacité naturelle d’écriture, jQuery ajoute le principe
du chaînage des appels. Chaque méthode de manipulation
retourne les éléments sélectionnés sur lesquels elle s’applique.
Ainsi, on peut exécuter plusieurs traitements sur le même sélecteur :
$("p").addClass("bord").addClass("fluo").removeClass("basique");

En une seule ligne, on ajoute les classes CSS bord et fluo et on


retire la classe basique de tous les paragraphes du document.

23.2.5 Quelques traitements avec jQuery

Abordons maintenant quelques traitements faciles à programmer


avec jQuery. Tous les exemples de ce paragraphe sont visibles sur
la page http://tjs.ovh/jquery.
◆ Sélectionner des éléments HTML

Le sélecteur jQuery supporte toutes les syntaxes CSS classiques


avec les classes et les identifiants. Il est aussi possible de chercher
des éléments grâce à leurs attributs. Par exemple, pour chercher
tous les div possédant l’attribut data-jour avec une valeur différente
de "0", on écrit :
$("div[data-jour][data-jour!='0']").addClass("fluo")

Pour sélectionner le nième élément d’une liste sélectionnée, on utilise


eq(n) :
$("div:eq(4)").addClass("shadow");

Le sélecteur peut aussi recevoir directement en paramètre un objet


JavaScript. Par exemple, pour manipuler l’objet document avec
jQuery, on écrit :
$(document)

◆ Attribuer des événements

Avant de manipuler des éléments de la page, il est fortement


recommandé de s’assurer que le document est correctement chargé
et prêt à être manipulé. jQuery propose l’événement ready sur
document :
$(document).ready(function(evt) {
console.log("Document chargé");
/* Les traitements peuvent être réalisés maintenant */
});

La méthode on() permet d’alimenter le gestionnaire d’événements :


$("div[data-jour]").on("click", function(evt) {
console.log("data-jour="+this.dataset.jour);
});

Ici, le clic sur les éléments div affiche dans la console la valeur de
leur attribut data-jour. Notez que, dans le corps de la fonction, this
est l’élément concerné par l’événement.
Le chaînage est aussi possible :
$("div[data-jour]").on("click", function(evt) {
console.log("data-jour="+this.dataset.jour);
}).on("mouseover", function(evt) {
$(this).addClass("shadow");
}).on("mouseout", function(evt) {
$(this).removeClass("shadow");
});

◆ Modifier des éléments

La méthode htm() de jQuery permet d’accéder et de modifier le


contenu innerHTML d’un élément du document :
String $(selector).html() /* Lecture du contenu HTML */
$(selector).html(String contenuHTML) /* affectation du contenu HTML */

Les propriétés de styles CSS sont accessibles et modifiables avec la


méthode css() :
String $(selector).css(String nomProprieteCSS) /* Lecture de la propriété */
$(selector).css(String nomProprieteCSS, String valeur) /* Affectation */

De la même manière, les attributs de balises sont accessibles et


modifiables avec la méthode attr() :
String $(selector).attr(String nomAttribut) /* Lecture valeur attribut */
$(selector).attr(String nomAttribut, String valeur) /* Affectation */

◆ Animer des éléments

jQuery propose des effets d’animations prêts à l’emploi pour cacher


ou faire apparaître des éléments.
Les méthodes show(), hide() et toggle() font apparaître ou
disparaître un élément immédiatement.
Les méthodes fadeIn() et fadeOut() ajoutent un effet de fondu, via la
propriété CSS opacity :
$(selector).fadeIn(Number delai [, String easing, [Function termine]])

La méthode attend trois paramètres :


Un delai, en millisecondes.
Une chaîne easing, définissant la forme de la fonction de
temps.
Une fonction termine(), facultative, qui s’exécute à la fin de
l’effet.
D’autres effets d’apparition jouant sur la taille sont aussi prévus avec
slideDown() et slideUp().
Enfin, animate() permet de créer des effets personnalisés sur les
propriétés CSS de l’objet.
Dans l’exemple http://tjs.ovh/jquery le clic et le rollover sur les div 1 à
9 utilisent plusieurs effets d’animation.

◆ Animations avec animate()

La méthode animate() de jQuery permet de déclencher des


animations sur un élément depuis l’état actuel vers de nouvelles
valeurs de propriétés CSS :
$(selecteur).animate(JSON propriete [, JSON options])

Par exemple, pour redimensionner le div#js vers une taille de 300 ×


300 pixels, on écrit :
$("div#js").animate(
{
width:300,
height:300,
},
{
duration: 2000,
easing: easingName,
complete: function() {
console.log("Fin d'animation de div#js");
}
}
);

Le premier paramètre de animate() est un objet JSON avec les


propriétés CSS finales à atteindre en fin d’animation. Le second
paramètre définit le comportement de l’animation avec la propriété
duration, la propriété easing qui contient la fonction d’accélération de
l’animation et la fonction complete à déclencher quand l’animation
est terminée.
Retrouvez l’animation à la page http://tjs.ovh/animate.

Notez que la méthode animate() peut souvent être remplacée par


des animations en CSS abordées dans l’annexe.

◆ Lancer un appel AJAX

jQuery n’oublie pas les traitements AJAX et met à profit sa


compacité d’écriture pour simplifier leur usage. Les appels AJAX
natifs via XMLHttpRequest ont été détaillés au chapitre 14 à partir
d’ici.
Reprenons notre exemple de recherche du cours du Bitcoin temps
réel en utilisant des appels AJAX via jQuery.
function jQueryBTC() {
$.ajax({
url: "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=EUR",
cache: false,
method: "GET",
dataType: "json"
}).done(function( data ) {
/* Fonction à réaliser lors de la réussite de l'appel */
console.log( data );
if (data.EUR) {
$("div#bitcoin").html(data.EUR.toFixed(2)+" &euro;");
}
}).fail(function() {
/* Fonction à réaliser si une erreur est détectée */
console.log("Erreur de retour AJAX");
});
}

La méthode ajax() attend en paramètre un objet JSON définissant le


type d’appel avec l’url, le type GET ou POST et le type de retour
attendu.
La méthode done() définit la fonction à exécuter lorsque l’appel est
terminé. Le paramètre data contient le retour de l’appel.
La méthode fail() définit la fonction à exécuter lorsque l’appel AJAX
échoue.

23.2.6 L’écosystème jQuery

jQuery bénéficie d’un écosystème extrêmement riche et de milliers


de développeurs qui partagent leurs créations. Si vous avez besoin
d’une fonctionnalité, il est très probable qu’un plugin jQuery soit déjà
disponible en ligne pour y répondre. Reportez-vous au répertoire des
plugins du site officiel https://plugins.jquery.com/ pour trouver des
fonctionnalités d’animations, de mise en page, de contrôle de
formulaires…
jQuery est souvent utilisé en combinaison de la bibliothèque jQuery
UI (pour User Interface), qui ajoute des fonctionnalités dédiées à la
manipulation d’éléments d’interface avancés, comme des onglets,
des listes et des tableaux triables, des saisies de dates via
calendrier… Afin de ne pas alourdir le poids de la bibliothèque, vous
pouvez choisir uniquement les éléments dont vous avez besoin et
télécharger uniquement ce qui est nécessaire à votre projet.
Il est à noter aussi que l’équipe jQuery a tenté de produire un
framework optimisé pour la navigation mobile et tactile
(https://jquerymobile.com/) avec nettement moins de succès et un
développement à l’arrêt aujourd’hui.
23.2.7 L’avenir de jQuery

À l’origine de jQuery, l’environnement de développement frontend


était très différent de celui d’aujourd’hui :
Des incompatibilités très fortes entre navigateurs rendaient la
programmation avancée vraiment chronophage et incertaine.
La sélection d’éléments via les sélecteurs CSS n’était pas
encore intégrée nativement au langage.
jQuery répondait parfaitement à ces difficultés, en y ajoutant une
syntaxe lisible, élégante et compacte. Son succès a été rapide et
maintenant encore de nombreux sites utilisent jQuery sur l’ensemble
de leurs pages.
La tendance est pourtant à un recul des usages au profit d’autres
frameworks plus intégrés ou, au contraire, à l’usage systématique du
langage JavaScript natif, comme nous le verrons au paragraphe
consacré à Vanilla.js.
Le principe de sélecteurs CSS avancés de jQuery reste vraiment
efficace. Ainsi, la bibliothèque Sizzle (http://sizzlejs.com/) propose,
dans seulement 4 ko de code, l’ensemble des fonctionnalités de
recherche d’éléments dans le document.

_ 23. 3 PROTOTYPE.JS
Le framework Prototype.js (http://prototypejs.org) a connu une
assez bonne visibilité il y a quelques années. Il a été utilisé par
d’autres bibliothèques assez connues, comme ScriptAcoulUs
(https://script.aculo.us), destinée à réaliser des interfaces utilisateurs
et des animations « spectaculaires ». Mais, comme la dernière
version de Prototype.js date déjà de 2015, on peut parier sur un
abandon définitif de son développement.
_ 23.4 REACT
React (site officiel https://reactjs.org) est une bibliothèque
JavaScript, développée et rendue publique par Facebook à partir de
2013, facilitant la création d’interfaces utilisateurs. Elle permet de
créer des interfaces riches, sur une seule page, à partir de petits
modules indépendants appelés components (« composants » en
français) qui se chargent de modifier le contenu de la portion de
page nécessaire.

Logo officiel React

23.4.1 Pourquoi choisir React ?

React, aussi appelée React.js ou ReactJS, est un produit robuste et


validé, conçu par les meilleurs développeurs du moment, et utilisé
en production par le site le plus consulté au monde.
React est un framework front-end, c’est-à-dire qu’il s’exécute du côté
de l’utilisateur dans le navigateur.
Sa communauté d’utilisateurs est vraiment large, avec de
nombreuses ressources, module et tutoriaux en ligne.
La durée de vie semble bonne à moyen terme.
L’inconvénient principal vient probablement de son fonctionnement
sous forme d’une application qui ne manipule qu’une seule page de
démarrage. Le référencement, qui n’est pas une contrainte pour un
réseau social, risque d’être plus délicat et plus incertain pour un site
classique.

23.4.2 Installation
Avec Node.js, vous pouvez utiliser le module create-react-app, qui
facilite la création d’un projet global.
Créez un nouveau répertoire pour votre application. Lancez le
terminal et exécutez la commande d’installation du module
(éventuellement avec sudo) :
npm install -g create-react-app

Placez-vous dans le répertoire de votre application et tapez la


commande suivante, qui crée notre application « first » :
create-react-app first

Cette commande crée l’arborescence de votre projet, avec tous les


fichiers nécessaires :

Le fichier package.json définit la configuration du projet Node.js. Le


fichier README.md donne de nombreuses informations sur la
manière de configurer le projet.
Le répertoire public contient toutes les ressources publiques
accessibles, comme la page de démarrage et l’icône de l’application.
Le répertoire src contient les sources du projet.
En tapant la commande Node.js suivante, vous générez une
compilation (build) de votre projet en mode développement. La
commande lance en même temps un serveur web disponible à
l’adresse localhost:3000 :
npm start
Le terminal affiche le message de confirmation suivant :

Dans votre navigateur, vous voyez votre première application, qu’il


reste à programmer.
Pour générer le projet en mode production, vous tapez la
commande :
npm run build

Le terminal affiche le résultat de la construction du projet :

MacBook-Pro-de-olivier:src olivier$ npm run build


> first@0.1.0 build /Users/olivier/cloud/react/first/first
> react-scripts build
Creating an optimized production build...
Compiled successfully.
File sizes after gzip:
36.94 KB build/static/js/main.a285be49.js
299 B build/static/css/main.c17080f1.css

Un répertoire build apparaît dans votre projet avec l’ensemble des


ressources à envoyer sur votre serveur de production :
On note que les fichiers de ressources .css et .js sont nommés de
manière aléatoire, afin de garantir qu’ils seront bien chargés depuis
le serveur, et non pas issus d’une ancienne version conservée en
cache.

23.4.3 La syntaxe de React

Observons le fichier index.js du répertoire src. On reconnaît la


syntaxe d’importation de modules de Node.js (voir le paragraphe des
modules au chapitre dédié) :
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));

Les objets React et ReacDom sont issus des modules internes du


framework React. L’import du fichier index.css générera lors de
la construction de la build un appel via la balise link.

23.4.4 React avec JSX

Si le format JSX n’est pas indispensable pour travailler avec React, il


est tellement utilisé qu’il serait dommage de s’en passer. Reportez-
vous ici du chapitre précédent pour en savoir plus sur ce format.
Dans le fichier ./src/App.js se trouve le cœur des traitements de
notre application. L’objet App est issu de la classe Component de
React :
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Bienvenue dans <strong>first</strong>,
notre première application React</h1>
</header>
<p className="App-intro">
Pour démarrer, éditez le fichier <code>src/App.js</code> et sauvegardez-
le.
Le changement sera détecté automatiquement et la page rechargée.
</p>
</div>
);
}
}

La méthode render() de App retourne du code HTML au format JSX.


Elle est appelée dans le fichier ./src/index.js avec la ligne :
ReactDOM.render(<App />, document.getElementById('root'));

Cet appel place dans l’élément #root de index.html le code HTML


retourné par App.render().

Grâce à Node.js, toute modification sauvegardée dans l’un des


fichiers code source du répertoire src est instantanément
détectée et prise en compte dans le navigateur.

23.4.5 Notre premier composant

Nous allons utiliser React pour afficher le cours en temps réel du


Bitcoin. Pour cela, nous aurons besoin d’un composant Bitcoin et
d’un appel AJAX au service de récupération du cours.
Pour définir notre composant Bitcoin, nous avons besoin d’un
nouveau fichier Bitcoin.js :
import React, { Component } from 'react';
import "./Bitcoin.css";
class Bitcoin extends Component {
constructor(props) {
super(props); /* Appel du constructeur de Component */
let delai=30000; /* Délai entre AJAX de 30 secondes par défaut */
if (props.delai) { /* Si la propriété delai est définie */
delai=parseInt(props.delai, 10);
}
this.state = {
delai: delai,
nbAjax: 0
};
console.log("Delai entre appels AJAX de "+this.state.delai+" ms");
}
render() { /* Affichage dans le DOM */
let cours="Recherche";
if (typeof this.state.EUR !== undefined) {
cours=this.state.EUR;
}
return (
<div id="Bitcoin">{this.formatMontant(cours)} &euro;</div>
);
}
}
export default Bitcoin;

Nous définissons dans Bitcoin.css les propriétés de style du


composant.
Dans le fichier central App.js, nous importons Bitcoin avec :
import Bitcoin from './Bitcoin';

Nous pouvons alors déclarer notre composant avec sa propriété


delai dans la fonction render() de App :
<Bitcoin delai="15000"/>

Il reste à gérer l’appel AJAX et le traitement de retour des données


avec de nouvelles méthodes dans la classe Bitcoin.
La méthode componentDidMount() s’exécute quand le composant
est créé pour la première fois dans le DOM. C’est à ce moment qu’il
faut déclencher l’appel AJAX et la minuterie :
componentDidMount() { /*Déclenchée quand le composant est créé dans le DOM*/
this.callAjax(); /* Appel Ajax immédiat */
setInterval(() => this.callAjax(), this.state.delai); /*Appel récurrent*/
}

La méthode de l’appel AJAX est assez lisible. À la fin des différentes


étapes de l’appel, définies par les then(), la propriété cours, issue du
service externe, est mise à jour :
callAjax() { /* Méthode pour l'appel AJAX au service externe */
fetch("https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=EUR")
.then(res => res.json())
.then(
(result) => {
this.setState({
EUR: result.EUR,
nbAjax: this.state.nbAjax+1,
dtMaj: (new Date()).getTime()
});
},
(error) => {
console.log("Erreur d'appel AJAX");
this.setState({
isLoaded: true,
error
});
}
);
}
La méthode componentDidUpdate() est exécutée quand le
composant, son contenu ou une de ses propriétés est modifié. Elle
se contente d’appeler à nouveau la fonction this.render() pour mettre
à jour l’affichage du composant.
Voici le rendu de notre application :

Le code source de cette application first est accessible sur


http://tjs.ovh/react.

23.4.6 L’outil React Developer Tools

L’outil React Developer Tools (http://tjs.ovh/reactdev) ajoute un


onglet à la console du navigateur et affiche les propriétés des
composants React pendant la navigation :

23.4.7 L’avenir de React

L’outil React Developer Tools sur Chrome compte plus d’un million
d’utilisateurs. On peut donc imaginer que l’écosystème React,
soutenu par Facebook, est riche de millions de développeurs.
Le graphe Google Trends montre que la popularité est encore en
hausse.
Le module React-native permet en plus de créer des applications
(voir le chapitre dédié).
Cette puissance garantit un avenir favorable à ce framework.

_ 23.5 ANGULAR
Angular (site officiel https://angular.io), créé par Google en 2010, est
un autre framework dédié à la création d’applications Internet. Il a
été conçu pour développer facilement et simultanément sur mobile
et poste fixe, tout en assurant de bonnes performances à
l’exécution.

Logo officiel de Angular

23.5.1 Pourquoi choisir Angular

Angular a été conçu en interne par Google, pour ses propres


besoins, par les meilleurs développeurs du monde.
La popularité du framework est forte, même si son potentiel de
croissance semble avoir atteint son maximum.
Angular utilise TypeScript, sur -ensemble du JavaScript (voir
chapitre sur les langages dérivés du JS), qu’il faut maîtriser.

23.5.2 Installation

Encore une fois, l’installation des composants de ce framework


s’appuie entièrement sur Node.js, qui doit être présent et à jour sur
votre machine (voir chapitre dédié). Installez Angular CLI (pour
Command Line Interface) avec la commande (éventuellement en
mode sudo) :
npm install -g @angular/cli

Créez un nouveau répertoire qui contiendra votre projet Angular.


Pour créer l’application « prime », tapez la commande :
ng new prime

Après environ une minute de téléchargements de ressources et de


traitements en local, votre projet est construit. À la fin de la création
du projet, une analyse de vulnérabilité est lancée. Si des failles sont
détectées, à cause de modules pas à jour, vous pouvez les détailler
avec la commande npm audit et les corriger avec la commande npm
audit fix.
Le répertoire de votre projet est structuré avec le classique
répertoire node_modules et un répertoire src qui contient
l’ensemble du code source du projet. Le répertoire e2e est destiné à
un usage avancé de tests End-to-End (ou tests de bout en bout, qui
vérifie qu’une application se comporte entièrement comme prévu).

Le répertoire contient de nombreux fichiers de configuration au


format .json, dont le classique package.json, qui définit les
dépendances Node.js. Le fichier angular.json définit les paramètres
du projet et le fichier tsconfig.json configure la compilation de
TypeScript.
Pour lancer votre projet à travers un serveur web accessible sur
http://localhost:4200, utilisez la commande :
ng serve
En ajoutant l’option -o (pour --open), votre navigateur s’ouvre sur la
bonne URL après la compilation :
ng serve --open

La compilation (build) du projet est réalisée avec la commande :


ng build

Le résultat de la build, à envoyer sur le serveur de production, est


stocké dans le nouveau répertoire dist :

Notez la présence de fichiers au format .js.map pour chaque


fichier .js compilé. Ces fichiers facilitent la recherche d’erreurs
lors des phases de tests.

L’application initiale de démonstration, qui ne contient qu’un logo et


quelques lignes de texte, nécessite neuf requêtes HTTP, dont cinq
pour des fichiers .js avec plus de 3 Mo de code source. Cette
structure est très lourde, lente à charger et à interpréter et est donc
pénalisante pour le référencement. Elle impose des optimisations
supplémentaires pour améliorer les performances.

23.5.3 Édition de notre application

Notre application contient la structure de fichiers suivante :


Comme pour React, l’ensemble des sources de l’application est
placé dans le répertoire src.
Le répertoire assets contient les ressources statiques (images,
logo, etc.) du projet.
Le répertoire environments définit les modes de compilation
développement ou production des builds.
Les fichiers placés directement à la racine définissent l’application.
Ils peuvent être regroupés dans trois familles. La première famille
définit son apparence :
Fichier dans src Utilité du fichier
Icône de favori (générée
favicon.ico automatiquement avec le logo
Angular)
Fichier HTML de démarrage de
index.html
l’application
Fichier TypeScript principal de
main.ts
l’application
Feuille de style générale de
styles.css
l’application
Une seconde famille garantit la compatibilité souhaitée entre
navigateurs :
Fichier dans src Utilité du fichier
browserlist Fichier texte définissant la cible des
navigateurs qui doivent être
compatibles avec le projet.
> 0.5%
last 2 versions
Firefox ESR
not dead
IE 9-11
Voir le paragraphe Babel pour
d’autres détails.
Fichier de surcharge TypeScript qui
garantit la compatibilité avec les
polyfill.ts navigateurs définis dans
browserlist. Voir définition de
polyfill.
Enfin, une troisième famille concerne l’utilisation avancée du
framework :
Fichier dans src Utilité du fichier
Point d’entrée du mode test de
test.ts
l’application
Configuration de la compilation
tsconfig.app.json
TypeScript de l’application
Configuration de la compilation
tsconfig.app.json
TypeScript en mode test
Configuration de l’outil TSLint
tslint.json
d’analyse de code TypeScript

23.5.4 Les composants Angular

Les composants sont placés dans le sous-répertoire app. À la


création du projet, le composant racine de l’application (root
component) est créé :

Un composant est constitué de quatre fichiers décrivant son


comportement, son apparence et ses caractéristiques :
Un fichier .css avec les propriétés de style.
Un fichier .html avec le code HTML du rendu du composant.
Un fichier .ts avec le code TypeScript décrivant son
comportement et ses paramètres d’entrée.
Un fichier .spec.ts avec les paramètres de compilation.
Le composant est créé en HTML dans le fichier index.html du
répertoire src avec :
<body>
<app-root></app-root>
</body>

Dans le fichier main.js du répertoire src, le fichier


src/app/app.module.ts est importé avec la ligne :
import { AppModule } from './app/app.module';

Le fichier app.module.ts contient la liste des composants utilisés


pour l’application :
import { AppComponent } from './app.component';

Le fichier app.component.ts décrit le fonctionnement du


composant :
import { Component } from '@angular/core';
@Component({
selector: 'app-root', /* Définit le tag HTML <app-root> */
templateUrl: './app.component.html', /* Template HTML */
styleUrls: ['./app.component.css'] /* Liste des fichiers CSS utiles */
})
export class AppComponent {
title = 'prime'; /* Paramètres d'entrées utiles dans le template */
}

Enfin, dans app.component.html, le paramètre title est affiché avec


la syntaxe doubles accolades {{ title }} :
Bienvenue dans notre application <strong>{{ title }}</strong> !

On observe avec cet exemple que le nombre de fichiers pour


créer et appeler un composant est vraiment élevé. Et nous ne
sommes pas entrés dans la partie configuration avancée de la
compilation…

23.5.5 Création d’un nouveau composant

Créons un nouveau composant Coins qui cherchera et affichera une


liste de crypto-monnaies. La création d’un nouveau composant peut
se réaliser avec une commande Angular dans le terminal :
ng generate component coins

Le terminal affiche les actions réalisées :

Les quatre fichiers sources du nouveau composant sont créés dans


src/app/coins/. Le fichier app.module.ts est mis à jour et prêt à
être importé avec la ligne :
import { CoinsComponent } from './coins/coins.component';

Pour intégrer le nouveau composant dans votre application, ajoutez


simplement la balise <app-coins></app-coins> dans
app.component.html.
En mode développement, c’est-à-dire avec la commande ng
serve, Node.js détecte la moindre modification du code source et
actualise la page dans le navigateur pour prendre en compte en
temps réel le contenu du répertoire src. Le terminal affiche les
détections de changement.

23.5.6 Édition de notre composant

Nous allons créer la classe Coin dans le fichier src/app/coin.ts :


export class Coin {
nom: string; /* Nom de la monnaie */
code: string; /* Code de la monnaie */
eur: number; /* Cours de la monnaie */
display: string; /* Chaîne à afficher dans le composant */
}

Dans notre composant, nous intégrons cette classe dans le fichier


coins.component.ts :
import { Component, OnInit } from '@angular/core';
import { Coin } from '../coin'; /* Importation de Coin */
import { COINS } from '../list-coins'; /* Importatation de la liste */
@Component({
selector: 'app-coins', /* Tag <app-coins> */
templateUrl: './coins.component.html', /* Fichier de template HTML */
styleUrls: ['./coins.component.css'] /* Fichiers css */
})
export class CoinsComponent implements OnInit {
coins= COINS;
constructor() { }
ngOnInit() { }
}

Le fichier list-coins.ts contient la liste des monnaies à afficher :


import { Coin } from './coin';
export const COINS: Coin[] = [
{code: "BTC", nom: "Bitcoin"},
{code: "ETH", nom: "Ethereum"},
{code: "XMR", nom: "Monero"},
{code: "ETC", nom: "Ethereum Classic"}
];

Le composant de liste est affiché grâce au template


coins.component.html :
<h2>Liste des crypto-monnaies</h2>
<ul class="coins">
<li *ngFor="let coin of coins">
<span class="coin">{{coin.code}}</span> {{coin.nom}}
</li>
</ul>

Retrouvez l’application Angular à l’adresse http://tjs.ovh/angular.

23.5.7 L’avenir de Angular

Angular est un outil puissant mais relativement complexe. Son


apprentissage implique de maîtriser TypeScript et une structure de
fichiers et de modules vraiment lourde. Il n’est pas surprenant que
React (voir paragraphe) passe devant lui en popularité (voir
graphique).

_ 23.6 VANILLA.JS
23.6.1 Le mouvement Vanilla JS

Le projet Vanilla JS (http://vanilla-js.com) est une réponse


humoristique à la prolifération de frameworks JavaScript. Il se
présente comme le framework le plus rapide, léger et compatible
jamais conçu. Il est tellement efficace que tous les navigateurs l’ont
intégré par défaut.
En fait, il s’agit simplement du langage JavaScript natif, sans
surcharge, sans fichier .js à inclure en début de page.
Le mouvement Vanilla JS défend un usage pur du JavaScript. Les
performances mesurées pour la manipulation d’éléments avec les
méthodes getElementById() ou getElementsByTagsName() sont au
moins dix fois meilleures qu’avec les différents sélecteurs des
frameworks.
Même la compacité d’écriture n’est pas vraiment pénalisée, en
comptant toutefois l’inclusion du script principal.

_ 23.7 QUELQUES BILIOTHÈQUES


JAVASCRIPT UTILES
L’écosystème JavaScript est à la fois immense et en perpétuelle
évolution. Il est impossible de lister toutes les bibliothèques
disponibles, même en se limitant aux plus robustes et durables. Ce
paragraphe présente simplement quelques trouvailles utiles,
classées selon leur usage. Si la plupart sont gratuites, certaines sont
proposées par des sociétés sur la base de licences annuelles
payantes. Ce modèle économique a l’avantage de garantir des
revenus aux développeurs et donc optimise les chances que le
projet sera maintenu dans la durée. La plupart de ces bibliothèques
sont installables avec Nodes.js (voir l’installation de modules).

23.7.1 La création graphique

La création de graphiques en JavaScript avec l’objet de bas niveau


Canvas (voir chapitre dédié) est fastidieuse. De nombreuses
bibliothèques ont été créées pour simplifier la génération de
graphiques. L’utilisateur affecte simplement des propriétés de style
et les données à afficher dans le graphique. La bibliothèque se
charge du rendu.
◆ amCharts

amCharts (https://www.amcharts.com) est une bibliothèque


spécialisée dans la représentation graphique de données avec des
dizaines de modèles de graphiques disponibles (courbe,
histogramme, camembert, carte…). Une version gratuite avec un
logo présent dans les rendus est disponible.

◆ vis.js

vis.js (http://visjs.org) est aussi un outil de visualisation graphique.


En plus des représentations classiques par courbes et
histogrammes, il ajoute la visualisation de données sous forme de
réseau ou d’organigramme et sous forme de timeline. vis.js est
gratuit.

◆ Chart.js

Chart.js (http://www.chartjs.org) est une petite librairie de création


graphique avec seulement une dizaine de types de graphiques, qu’il
est possible de mélanger dans un même rendu. Chart.js mise sur la
simplicité, le responsive design et sa gratuité.

◆ Dygraphs

Dygraphs (http://dygraphs.com) est une bibliothèque minimaliste


conçue pour l’affichage de données sous forme de courbes. Son
objectif est surtout la rapidité d’affichage, qui lui permet d’afficher
des dizaines de milliers de points de données sans ralentissement.
La performance des autres bibliothèques sur de gros volume de
données n’est pas toujours au rendez-vous. Dygraphs existe depuis
2011, est toujours maintenu régulièrement et reste gratuit.

◆ gmaps.js
gmaps.js (https://hpneo.github.io/gmaps) simplifie l’usage de
Google Maps pour la création de cartes basiques, d’itinéraires ou de
surcouches personnalisées.

◆ D3.js

La bibliothèque D3.js (https://d3js.org) est assez particulière. Son


nom vient des trois « D » de Data Driven Documents. Elle se
positionne entre un outil de représentation graphique et une
bibliothèque de manipulation des éléments du document, à l’image
de jQuery. Son fonctionnement d’assez bas niveau implique un
temps d’apprentissage assez long mais sa puissance permet
d’obtenir des rendus impressionnants. Ne manquez pas la galerie et
les centaines d’exemples du site officiel.

23.7.2 Éditeurs de texte enrichi

Les éditeurs de texte enrichi (Rich Text Editors) sont des interfaces
dédiées à la saisie de texte au format enrichi. Leur but est de faciliter
la mise en forme d’un document directement sur la page web. Ils
remplacent ainsi les éléments de formulaire de type textarea par des
interfaces complètes proches des traitements de texte. En raison de
leur complexité, ils sont souvent proposés en version payante.

◆ TinyMCE

TimyMCE (https://www.tiny.cloud) est la solution la plus connue et la


plus complète. Rien ne lui résiste.

◆ CKEditor

CKEditor (https://ckeditor.com) est l’autre poids lourd des éditeurs


de texte en ligne.
◆ Trumbowyg

Ce plugin jQuery très léger (https://alex-d.github.io/Trumbowyg/),


développé par un Français répondra aux besoins les plus
classiques.

23.7.3 Animations

Les animations en JavaScript sont réservées à des usages


particuliers de démonstration technologique ou de jeux.

◆ three.js

three.js (https://threejs.org) est la bibliothèque la plus puissante


pour réaliser des animations et des rendus en 3D. Son usage est
réservé aux développeurs avancés.

◆ Velocity.js

La bibliothèque Velocity.js (http://velocityjs.org) remplace les


animations de jQuery $.animate() (voir détail de la méthode) pour en
optimiser les performances.

◆ Anime.js

La bibliothèque Anime.js (http://animejs.com) propose une vaste


série d’animations d’éléments du document. Sa relative simplicité
permet de créer facilement des animations attrayantes.

23.7.4 Manipulation du document

La manipulation du document et des éléments qui le composent est


sans doute la partie qui propose le plus de librairies annexes. De
nombreuses librairies s’appuient sur un framework de base.
◆ jBox

jBox (https://stephanwagner.me/jBox) est un plugin jQuery qui gère


les info-bulles (tooltips), les notifications, les boîtes de dialogue et
même l’affichage d’album photos.

◆ jQueryUI

jQueryUI (https://jqueryui.com) est une extension de jQuery dédiée


à l’interface utilisateur avec des améliorations sur les éléments de
formulaire et sur des éléments d’interface comme des onglets, des
menus…

◆ Bootstrap

Bootstrap (https://getbootstrap.com) est une boîte à outils pour


développer des applications mobiles et responsive design.
24

Les Web Workers

Objectif
Dans ce chapitre, nous allons apprendre à utiliser les Web
Workers, qui sont un système d’exécution de scripts en
arrière-plan, sans impact sur la réactivité de l’interface
utilisateur.

_ 24.1 PRINCIPES DES WEB WORKERS


Les navigateurs ont comme objectif permanent de protéger leurs
utilisateurs contre les sites malveillants ou mal conçus. Ainsi, un
script qui s’exécute pendant une période trop longue est intercepté.
Le navigateur propose de l’interrompre en affichant ce type de
message :

Afin de permettre malgré tout aux développeurs de créer des scripts


réalisant des traitements lourds et longs, le JavaScript propose les
objets Web Workers. Grâce à eux, des scripts peuvent être exécutés
en arrière-plan, sans interférer sur la réactivité de l’interface
utilisateur, tout en communiquant avec la page principale grâce au
gestionnaire d’événements.

24.1.1 L’objet Worker

Le constructeur Worker attend en paramètre un nom de fichier de


type .js qui contient le code exécuté par le Web Worker :
var myWorker=new Worker("worker.js");

Les navigateurs récents supportent l’objet Worker. Pour éviter toute


erreur à l’exécution, il peut être utile de tester l’existence du
constructeur avant la création du Worker :
if (window.Worker) {
var myWorker=new Worker("worker.js");
}

Un objet Worker fonctionne à partir de réceptions et d’envois de


messages vers la page principale qui l’a construit. Le code décrivant
le fonctionnement du Worker est présent dans son fichier .js dédié.
La notation pointée habituelle est donc superflue ici.
Le gestionnaire d’événement onmessage détecte la réception d’un
message :
onmessage=function(evenement) {
console.log("Le message reçu est dans evenement.data");
console.log(evenement.data);
}

L’envoi de message est réalisé avec postMessage() :


myWorker.postMessage(JSON message);

Notez que le terme « message » est vraiment réducteur. Il est


possible d’envoyer des données complexes au format JSON
entre le Worker et la page principale.
La méthode terminate() détruit immédiatement l’instance du Worker :
myWorker.terminate() ;

24.1.2 Un Web Worker pour calculer une suite


de nombres

Nous allons créer un Worker pour calculer et afficher la suite de


Fibonacci, qui est une série de nombres définie par la formule :

Fn = Fn–1 + Fn–2

Cette formule signifie simplement que le nième nombre de la suite est


égal à la somme des deux nombres qui le précèdent. La suite vaut
donc :

1, 1, 2, 3, 5, 8, 13, 21…

Dans la page principale, nous créons notre Worker, qui envoie un


message de consigne de calcul de la suite et qui reçoit un message
de réponse avec le résultat à afficher :
if (window.Worker) {
var myWorker=new Worker("worker.js");

var fibonacci={
"type": "fibonacci",
"longueur": "30",
}
/* Envoyer un message avec la consigne de calcul de la suite */
myWorker.postMessage(fibonacci);
/* Réception du résultat */
myWorker.onmessage=function(evenement) {
/* Mise à jour du nombre d'éléments de la liste */
document.querySelector("span#longueur").innerText=evenement.data.longueur;
/* Construction de div#suite avec les lignes de résultats */
var suite=document.querySelector("div#suite");
var suiteTitre=document.querySelector("div#suiteTitre");
suite.innerText="";
suite.appendChild(suiteTitre);
for (var i=0; i<evenement.data.longueur; i++) {
/* Contenu de la boucle non reporté dans le livre */
}
}
}

Le fichier worker.js contient le traitement principal du Worker :


/* Script du Worker pour l'exemple de la page http://tjs.ovh/webworker */
onmessage=function(evenement) {
console.log("Le message reçu est dans evenement.data");
console.log(evenement.data);
var msg=evenement.data;
if (msg.type=="fibonacci") {
/* On commence avec un 0 pour éviter de sortir du tableau à l'indice 1 */
var s=[0,1];
for (var i=2; i<=msg.longueur; i++) {
s.push(s[i-1]+s[i-2]);
}
s.shift(); /* Supprime le premier élément technique valant 0 */
console.log(s);
msg.suite=s;
postMessage(msg); /* Envoi d'un message vers la page principale */
}
}

Notre script affiche les éléments de la suite :

Retrouvez l’ensemble du script à la page http://tjs.ovh/webworker.


24.1.3 Le schéma de principe

Voici le schéma de fonctionnement d’un Worker et des messages


entre le fichier .js associé et la page d’origine :

_ 24.2 UN CAS RÉEL DE WEB WORKER


Naturellement, notre précédent Worker n’est ici qu’un exemple du
fonctionnement par envoi de messages. Un Worker n’a d’intérêt que
pour un traitement qui risque d’être long, comme sur un jeu
d’échecs, où chaque coup de l’ordinateur peut prendre plusieurs
secondes de calculs intensifs.
Nous allons utiliser un Web Worker pour résoudre un tirage aléatoire
d’un tour du célèbre jeu « Le compte est bon ».

24.2.1 Les règles du jeu « Le compte est bon »

Le jeu commence par un tirage aléatoire de six plaques de nombres


parmi 24 plaques disponibles (deux fois les nombres de 1 à 10, et
une fois seulement les nombres 25, 50, 75 et 100). Un second tirage
donne un nombre cible entre 100 et 999.
Le but du jeu est de trouver l’ensemble des opérations
mathématiques utilisant les six nombres d’origine pour parvenir au
nombre cible. La division n’est autorisée que si le résultat est un
entier.
Si le résultat n’est pas atteint précisément, le plus faible écart
l’emporte. À résultat équivalent, la solution utilisant le moins de
plaques l’emporte.

24.2.2 L’algorithme de jeu et de résolution

Notre script commence par tirer au sort les plaques du jeu et le


résultat à obtenir, avec un affichage des plaques disponibles et du
tirage :

La recherche de la meilleure solution (ou de toutes les meilleures


solutions) nécessite de parcourir l’ensemble des possibilités de
calcul sur les six plaques et les quatre opérations mathématiques.
À chaque opération mathématique entre deux plaques, nous
générons un nouveau tableau de plaques disponibles et nous
relançons l’opération jusqu’à obtenir le résultat à atteindre ou jusqu’à
ce qu’il ne reste plus qu’une plaque.
Par exemple, pour la première opération entre les deux premières
plaques, nous avons comme calcul :

100 + 25 = 125

Le nouveau tableau de plaques à traiter devient :


125, 9, 8, 10, 2

Ce nouveau tableau qui passe à cinq éléments doit de nouveau être


traité, avec un nouveau calcul :

125 + 9 = 134

Le tableau de plaques disponibles se réduit encore :

134, 8, 10, 2

Ce principe de réduction du tableau à chaque traitement s’adapte


parfaitement à une fonction récursive, c’est-à-dire qui s’appelle
elle-même tant qu’un événement (ici l’absence de plaques à traiter
ou le but atteint) ne se produit pas.
/* Fonction récursive : parcourir les possibilités de réduction du tirage */
function chercherSolution(jeu, operations="") {
if (jeu.tours.length==0) {
var plaques=jeu.tirages;
} else {
var plaques=jeu.tours[jeu.tours.length-1].plaques;
}
if (plaques.length<=2) { // Il ne reste plus assez de plaques : fin de la recherche
return true;
}

// Tableaux des tours de la solution


var tours=[];

// Boucle sur la première plaque du calcul


for (var p1=0; p1<jeu.tirages.length; p1++) {
// Boucle sur la seconde plaque (indice p2 de p1+1 a n)
for (var p2=p1+1; p2<plaques.length; p2++) {
// Boucle sur les 4 operations
if (operations=="") {
operations=jeu.operateurs;
}
operations.forEach(function(ope, index) {
tours[p1+"-"+p2+"-"+index]=JSON.parse(JSON.stringify(jeu.tours));
// Réduction du tableau des plaques
if (tour(jeu, p1, p2, ope)!=jeu.but) { // Fonction de calcul d'une opération
// Application de l'opération du tour
chercherSolution(jeu); // Appel récursif
}
jeu.tours=tours[p1+"-"+p2+"-"+index];
});
}
}
}

Pour ce tirage d’exemple, notre script affiche les résultats, avec les
plaques d’origine sur fond noir et les plaques des calculs
intermédiaires sur fond blanc :

Une fonction de filtration des solutions de calcul doit être appliquée


pour éliminer :
Les solutions moins bonnes, utilisant plus de plaques.
Les solutions équivalentes, du fait des règles de distributivité et
de commutativité des opérations.

24.2.3 L’application dans un Web Worker

Même avec un ordinateur puissant moderne, le parcours de


l’ensemble des possibilités nécessite environ trois secondes,
pendant lesquelles notre page ne réagit pas aux interactions de
l’utilisateur. Avec un smartphone, le temps de résolution tourne
autour de vingt secondes. La création d’un Worker dédié à la
résolution du tirage prend ici tout son intérêt.
/* Lancement du worker dédié */
if (window.Worker) {
var myWorker=new Worker("worker-lecompteestbon.js");
/* Envoyer un message avec la consigne de calcul de la solution */
myWorker.postMessage(jeu);
/* Réception du message avec le résultat */
myWorker.onmessage=function(evenement) {
printSolution(evenement.data);
}
} else {
alert("Navigateur trop ancien");
}

Retrouvez le script complet à l’adresse http://tjs.ovh/lceb.

24.2.4 La programmation multi-threads

Afin d’être encore plus rapide dans le traitement, nous pouvons


séparer le traitement de recherche en plusieurs processus
simultanés (appelés threads). Chaque processus se charge de
traiter une partie du problème. Une fois que tous les processus ont
répondu, il reste à concilier leurs solutions et à filtrer les meilleures.
L’algorithme de résolution de notre exercice de calcul démarre avec
la première plaque et enchaîne à la suite les quatre opérations
mathématiques disponibles sur la seconde plaque. Nous pouvons
donc séparer notre traitement en quatre processus à exécuter
simultanément. Chaque processus traitera une opération
mathématique entre la première et la seconde plaque et poursuivra
la réduction du tableau des résultats jusqu’à épuisement des
plaques. Le processus chargé de la division sera la plupart du temps
instantané, car les probabilités d’obtenir un reste entier sur deux
plaques sont faibles.
La console affiche, pour le même tirage initial, une performance bien
meilleure avec quatre processus :

Le temps total du traitement est celui du processus le plus lent.


Pour obtenir les meilleures performances, il faut donc réussir à
équilibrer au mieux les traitements par processus.

Une version interactive du script avec la possibilité de choisir son


tirage est disponible à l’adresse http://tjs.ovh/lecompteestbon.
25

Créer une extension


de navigateur

Dans ce chapitre, nous allons apprendre à créer une


extension pour les navigateurs.

_ 25.1 JAVASCRIPT DANS UN LIEN


DE LA BARRE DES FAVORIS
Avant de créer une extension de navigateur, nous allons ajouter
dans la barre de favoris un raccourci qui exécutera notre code
JavaScript sur la page en cours de consultation. Ce type de favori
s’appelle un bookmarklet.

25.1.1 L’opérateur void

Le JavaScript propose l’opérateur void, qui a pour objectif de


retourner la valeur non définie undefined. La syntaxe de void est la
suivante :
var a=1;
void a; // Retourne undefined

Généralement, void est utilisé avec les parenthèses :


var a=1 ;
void(a=2); // Retourne undefined, mais a vaut maintenant 2

25.1.2 La création du lien d’action JavaScript

L’opérateur void est indispensable pour utiliser du JavaScript dans


l’attribut href d’une balise a :
<a href="javascript:void(document.body.style.background='#ccc')">Fond gris</a>

Un clic sur ce lien exécute la modification de la couleur de fond du


corps de la page et retourne undefined.
Sans l’opérateur void, l’exécution de l’expression JavaScript
retournerait #ccc, qui remplacerait le corps de la page existant.
Nous allons créer un raccourci qui affiche notre adresse IP dans une
zone du corps de la page. Pour commencer, définissons une
fonction anonyme auto-exécutée qui crée une iframe, lui affecte des
dimensions, un style CSS fixe en haut de page et une adresse vers
une page de toutjavascript.com qui affiche l’IP de l’utilisateur :
(function(){
iframe=document.createElement("iframe");
iframe.setAttribute("width", "210");
iframe.setAttribute("height", "40");
iframe.setAttribute("style", "position:fixed;left:2px;top:2px;border:1px solid
#000;background:#fff;z-index:10000;");
iframe.setAttribute("src", "https://www.toutjavascript.com/api/iframeip.php");
document.body.appendChild(iframe);
})()

Pour ajouter cette fonction dans la balise a, il faut l’encoder avec la


fonction JavaScript encodeURIComponent(), sans oublier l’opérateur
void.
Le lien devient alors :
<a class="btn"
href="javascript:void((function()%7Biframe%3Ddocument.create
Element(%22iframe%22)%3Biframe.setAttribute(%22width%22%2C%20%22210%2
2)%3Biframe.setAttribute(%22height%22%2C%20%2240%22)%3Biframe.setAttribut
e(%22style%22%2C%20%22position%3Afixed%3Bleft%3A2px%3Btop%3A2px
%3B%20border%3A1px%20solid%20%23000%3Bbackground%3A%23fff%3Bz-
index
%3A10000%3B%22)%3Biframe.setAttribute(%22src%22%2C%20%22https%3A%2
F%2Fwww.toutjavascript.com%2Fapi%2Fiframeip.php%22)%3Bdocument.body.app
endChild(iframe)%3B%7D)())">Mon IP</a>

Retrouver notre lien à ajouter aux favoris à l’adresse


http://tjs.ovh/jsfavori.

25.1.3 L’ajout du lien dans la barre de favoris

L’ajout du raccourci se fait simplement par un drag and drop du lien


vers la barre de favoris :

Une fois le lien présent dans la barre de favoris, il suffit de le cliquer


pour que son code JavaScript s’exécute dans le corps de la page en
cours de consultation :

25.1.4 Les limites de sécurité

Cet exemple ne fait qu’injecter une iframe dans le corps de la page


et ne présente aucun risque de sécurité. Mais il est possible
d’injecter de la même manière des fichiers JavaScript externes. Un
code JavaScript externe exécuté sur un autre domaine est
potentiellement très dangereux. Il permet à des développeurs
malveillants de voler les données du site comme les cookies ou les
données stockées dans localStorage. Certaines attaques ont montré
aussi qu’il était possible de voler les mots de passe préremplis dans
les formulaires de connexion.
Pour limiter l’exécution de scripts externes potentiellement
dangereux, l’en-tête HTTP content-security-policy définit ce que le
navigateur accepte d’afficher dans la page. Par exemple, Facebook
n’autorise que quelques domaines pour les scripts externes :

content-security-policy: script-src *.facebook.com *.fbcdn.net


*.facebook.net *.google-analytics.com *.google.com

_ 25.2 CRÉER UNE EXTENSION


SUR GOOGLE CHROME
Notre bouton de raccourci n’est pas très pratique à installer ni à
utiliser. De plus, il risque d’être bloqué par les navigateurs. Tous les
navigateurs proposent à leurs utilisateurs d’installer des extensions,
diffusées dans un catalogue officiel, faciles à installer, avec des
niveaux d’accès et de risques connus à l’avance.

25.2.1 À quoi sert une extension ?

Une extension est un module proposant de nouvelles fonctionnalités


à l’utilisateur du navigateur. De nombreuses extensions sont dédiées
aux développeurs web, comme Web Developer (voir ici) ou comme
les extensions de Google pour mesurer la performance d’une page
au niveau référencement.
D’autres sont destinées au grand public, comme les extensions de
gestion de documents dans le Cloud, de Cashback ou des bloqueurs
de publicité type Adblock (voir ici).

25.2.2 Créer sa première extension pour Chrome


Nous allons remplacer notre bouton de raccourci JavaScript par une
extension pour Chrome, qui affichera l’adresse IP de l’utilisateur
dans une petite fenêtre web. Le but est d’obtenir, dans le
prolongement de la barre de navigation, un bouton qui ouvre notre
extension :

◆ Le contenu d’une extension

Une extension est composée de fichiers de ressource web, qui


seront ensuite regroupés dans un package pour la diffusion sur la
boutique du navigateur. Une extension possède au minimum cinq
fichiers :
Un fichier index.html, page de démarrage de l’extension.
Un fichier script.js, contenant les instructions JS.
Un fichier style.css, contenant les feuilles de style.
Un fichier icon.png, représentant l’icône du bouton.
Un fichier manifest.json, définissant les caractéristiques de
l’extension.
Si nécessaire, d’autres fichiers peuvent être ajoutés et organisés
dans des sous-répertoires, par exemple, des sources de
frameworks.

◆ Le manifeste

Le premier fichier à créer est manifest.json, fichier au format JSON


contenant le manifeste de l’application (analogie à la terminologie du
monde du transport de marchandises, pour lequel le manifeste
déclare la cargaison du navire). Pour notre extension, le manifeste
contient :
{
"manifest_version": 2,
"name": "Get My IP",
"version": "0.0.2",
"author": "ToutJavaScript.com",
"description": "Affiche votre adresse IP en un seul clic",
"content_security_policy": "default-src 'self' https://www.get-my-ip.com",
"browser_action": {
"default_title": "Get My IP",
"default_icon" : "ip-48.png",
"default_popup": "index.html"
},
"icons": {
"16": "ip-16.png",
"48": "ip-48.png",
"128": "ip-128.png"
}
}

◆ Quelques contraintes

La création d’extensions implique quelques contraintes. Il est par


exemple interdit de définir du code JavaScript ou des feuilles de
style dans le corps de index.html. De la même manière, il est
interdit de déclarer du JavaScript ou des propriétés CSS dans les
attributs style ou onevent. Pour résumer, chaque fichier doit contenir
le type de contenu qu’il est supposé recevoir.

25.2.3 Créer et tester une extension

◆ Créer votre extension


La page index.html est affichée dans la boîte de dialogue qui
s’ouvre au clic sur le bouton de notre extension. Dans le fichier
getip.css, nous définissons la taille de la boîte avec :
body {
width:200px;
height:140px;
}

La page index.html contient l’appel aux fichiers utiles pour


l’extension et les différents éléments HTML qui seront manipulés.
Parmi les éléments importants :
div#myip est la zone d’affichage de l’IP.
div#help est la zone de texte d’aide sous l’IP.
div#reload est le bouton de rafraîchissement de l’adresse IP.
div#about est le bouton qui affiche le bloc « A Propos »
div#info.
Le fichier getip.js définit les actions des différents événements et
lance le chargement de l’IP lorsque le document est prêt :
document.addEventListener("DOMContentLoaded", function() {
/* Déclaration des événements */
document.querySelector("div#reload").addEventListener("click", loadGetMyIP);
document.querySelector("div#myip").addEventListener("click", copyMyIP);
document.querySelector("div#about").addEventListener("click", function() {
document.querySelector("div#info").style.display="block";
});
document.querySelector("div#close").addEventListener("click", function() {
document.querySelector("div#info").style.display="none";
});
document.querySelector("#myip").addEventListener("mouseenter", function() {
document.querySelector("div#help").style.color="#333";
});
document.querySelector("#myip").addEventListener("mouseleave", function() {
document.querySelector("div#help").style.color="#aaa";
});
/* Chargement initial de l'adresse IP */
loadGetMyIP();
});

La fonction loadGetMyIP() est un appel AJAX qui charge le contenu


de https://www.toutjavascript.com/api/get-my-ip.php dans div#myip.
Vous pouvez voir l’ensemble des codes sources de ces fichiers en
ligne et les télécharger sur http://tjs.ovh/extension.

◆ Tester l’extension en mode développeur

Tapez dans la barre de navigation de Google Chrome l’adresse :

chrome://extensions/

Une page dédiée aux extensions déjà installées est affichée.


Activez le mode développeur via l’interrupteur pour faire apparaître
les menus dédiés.
Cliquez ensuite sur le bouton « Chargez l’extension non
empaquetée » pour sélectionner le répertoire contenant votre
extension.
Si aucune erreur n’est rencontrée, votre extension est activée
immédiatement et son bouton apparaît à droite de la barre de
navigation. Notre extension apparaît dans la liste :

Chaque modification dans le code source du répertoire est reportée


en temps réel dans votre extension, ce qui facilite grandement le
développement et le test.
De plus, la console du navigateur est accessible aussi sur le corps
des extensions.

25.2.4 Les fonctionnalités dédiées aux extensions

L’objet principal chrome donne accès à des fonctionnalités dédiées


aux extensions. Par exemple, pour récupérer les données du fichier
manifest.json dans un objet JSON, on utilise la méthode
getManifest() sur runtime :
var manifestData = chrome.runtime.getManifest();

Grâce à l’objet chrome, il est possible d’accéder à l’ensemble des


informations du navigateur, d’ajouter des boutons d’action, de
modifier le comportement natif du navigateur et d’interagir en temps
réel avec la navigation de l’utilisateur.
Pour protéger les données de l’utilisateur, les extensions utilisent un
système précis de permissions pour chaque type d’accès aux
données des internautes.

25.2.5 Publier une extension

Naturellement, le but du développement sur Internet est très souvent


de partager ses créations avec le monde entier. Google propose
dans son Chrome Web Store une interface de recherche
d’extensions et d’applications pour les utilisateurs.
Une fois que votre extension est finalisée en mode développeur, il
reste à la partager. Nous allons rapidement indiquer les principales
étapes de sa publication :
Créer une simple archive ZIP du répertoire de l’extension.
Créer un compte développeur sur
https://chrome.google.com/webstore/developer/dashboard (ce
type de compte nécessite un paiement modeste de 5 $ afin de
limiter les publications d’extensions sans intérêt).
Enregistrer les informations de votre extension en mode
brouillon avec des captures d’écran, un descriptif et des pages
de contacts.
Rendre votre extension publique.
Votre tableau de bord affiche ce type de listing :

L’extension complète (avec géolocalisation et multilingue) Get-


My-IP est publiée sur le Chrome Web Store à l’adresse
http://tjs.ovh/getmyip.

25.2.6 Une extension qui communique avec l’onglet


actif

L’extension Get-My-IP ne nécessite aucune permission particulière,


car elle ne modifie pas le comportement du navigateur, ni la page en
cours de consultation.
Bien sûr, il est possible d’interagir avec la page en cours de
chargement, en ajoutant la permission suivante dans le fichier
manifest.json :
"permissions": ["activeTab"]

Nous allons démarrer la programmation d’une nouvelle extension


destinée à compter et surligner les balises headers h1 et h2 dans la
page courante. La structure des fichiers de cette nouvelle extension
est la suivante :
Le fichier manifest.json fait apparaître deux nouveautés :
{
"manifest_version": 2,
"name": "__MSG_extTitle__",
"version": "0.0.3",
"author": "__MSG_extAuthor__",
"description": "__MSG_extDescription__",
"permissions": ["activeTab"],
"browser_action": {
"default_title": "SEOnline",
"default_icon" : "icon-headers-128.png",
"default_popup": "index.html"
},
"background": {
"scripts": ["assets/background.js"],
"persistent": false
},
"icons": {
"128": "icon-seo-128.png"
},
"default_locale": "en"
}

◆ La gestion des traductions

Les libellés de traductions sont stockés dans des fichiers JSON du


répertoire principal _locales. Le fichier messages.json du répertoire
_locales/fr contient :
Chaque élément de libellé est identifié par un nom avec un message
et sa description. La description n’est utile que pour le traducteur du
fichier.
{
"extTitle": {
"message": "ViewHeaders",
"description": "Titre de l'application"
},
"extDescription": {
"message": "Compte et surligne les balises Headers H1 et H2 dans la page",
"description": "Description rapide de l'application"
}

}

La récupération de la valeur du libellé nommé extTitle se fait de


différentes manières selon le contexte. Dans le fichier
manifest.json, le libellé est contenu dans la chaîne
__MSG_extTitle__.
Dans les fichiers JavaScript de l’extension, il faut utiliser la méthode
getMessage() de l’objet i18n :
chrome.i18n.getMessage("extTitle")

Le navigateur détecte automatiquement la langue de l’utilisateur et


charge le bon fichier de messages. Si la langue ne correspond à
aucun des fichiers disponibles, le fichier défini dans le paramètre
default_locale est utilisé.

Notez que i18n est une abréviation courante en informatique pour


définir les fonctionnalités d’internationalisation et de traduction
d’applications. Le « 18 » fait référence au nombre de caractères
entre le premier i et le dernier n du mot internationalization.

◆ Le fichier JavaScript d’arrière-plan


Le script background.js (« arrière-plan » en français) défini dans le
manifest.json est utilisé pour la réception de messages entre
l’onglet actif et le pop-up de l’extension :
/* Détecte la réception de messages vers l'extension */
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse){
if (message.popupOpen) { /* Message à l'ouverture du pop-up de l'extension */
chrome.tabs.getSelected(null, function(tab){
chrome.tabs.executeScript({ file: "assets/inject.js" });
chrome.tabs.insertCSS({ file: "assets/inject.css" });
});
}
});

L’appel à chrome.tabs.getSelected() détecte l’onglet actif. L’injection


des fichiers .js et .css dans le document actif est réalisé par
chrome.tabs.executeScript() et chrome.tabs.insertCSS().
Depuis le fichier background.js, on remonte à la fenêtre principale
pop-up de l’extension avec :
if (message.showHeaders) {
var views = chrome.extension.getViews({ type: "popup" });
var popup=views[0];
popup.updateExtension(message.showHeaders);
}

Attention, comme le fichier d’arrière-plan est toujours chargé, les


modifications en temps réel ne fonctionnent pas ici. Il faut penser
à mettre à jour l’extension dans l’interface chrome://extensions/.

◆ La communication par messages

L’extension et l’onglet actif communiquent par des messages


envoyés vers background.js avec sendMessage(), qui reçoit en
paramètre un objet JSON.
Dans l’extension, on écrit :
/* Envoyer le message "popupOpen" dans le background.js de l'extension */
chrome.runtime.sendMessage({popupOpen: true});

Dans l’onglet actif, à la fin du fichier .js injecté, on écrit :


/* Identification des balises Headers 1 et 2 */
var H1s=document.getElementsByTagName("h1");
for (var i=0; i<H1s.length; i++) {
var h=H1s[i].style.outline="2px dotted #f00";
}
var H2s=document.getElementsByTagName("h2");
for (var i=0; i<H2s.length; i++) {
var h=H2s[i].style.outline="2px dotted #0a0";
}
/* Envoyer vers le pop-up de l'extension via background.js */
chrome.runtime.sendMessage({showHeaders: {nbH1: H1s.length , nbH2:
H2s.length}});

Retrouvez cet exemple complet à l’adresse http://tjs.ovh/headers.

Si vous êtes intéressé par le référencement et les bonnes


pratiques SEO, découvrez et installez mon extension SEO
disponible à l’adresse http://tjs.ovh/seo.

Schéma de principe de communication entre la fenêtre de l’extension,


le fichier background.js et l’onglet actif

25.2.7 Les extensions dans d’autres navigateurs


Tous les navigateurs proposent de développer et de publier des
extensions. Comme il n’existe aucun standard, chaque éditeur de
navigateur propose sa propre technologie et tous imposent de créer
un compte développeur, parfois payant :
Firefox est le plus avancé avec un assistant de migration des
extensions Chrome à l’adresse https://www.extensiontest.com/.
Il suffit d’y uploader le fichier .crx de votre extension
(téléchargeable dans le Chrome Developer Dashboard) pour
obtenir une analyse de votre extension pour Firefox.
Safari impose d’utiliser son énorme environnement de
développement maison Xcode, qui nécessite un apprentissage
assez fastidieux.
Microsoft Edge propose un outil de migration d’extensions
Chrome.
Opera est très peu utilisé.
Internet Explorer est en fin de vie, car même Microsoft pousse
ses clients vers Edge et la création d’extension nécessite de
programmer en langage C.

Ce chapitre n’est qu’une introduction à la création d’extensions.


Les possibilités offertes par les navigateurs sont pratiquement
aussi larges que la création de sites Internet.
26

Créer des applications

Objectif
Dans ce chapitre, nous allons aborder la création d’une
application native, au sens d’un exécutable, sur les systèmes
d’exploitation de bureau Windows et macOS et sur les
environnements mobiles Android et iOS.
Le but de ce chapitre n’est pas de créer une application de
bout en bout mais de montrer toutes les possibilités du
langage JavaScript.

_ 26.1 CRÉATION D’APPLICATIONS


DE BUREAU
Il existe plusieurs solutions techniques pour créer une application de
bureau. Nous allons étudier le framework Electron
(https://electronjs.org), qui transforme du code HTML, CSS et JS en
exécutables pour Windows ou macOS. Electron est largement utilisé
et sert de base pour des applications installées par des millions
d’utilisateurs, comme la messagerie Discord.

26.1.1 Création de l’application


Une application Electron est au départ une application Node.js (voir
chapitre dédié).
Créez la structure de votre application dans un répertoire dédié avec
la commande du terminal :
npm init

Un fichier package.json est créé. Modifiez-le afin d’ajouter le bloc :


"scripts": {
"start": "electron ."
}

26.1.2 Installation de Electron

Utiliser la commande suivante pour installer le module Electron dans


votre projet :
npm install --save-dev electron

Créez votre fichier index.js pour utiliser le module Electron, qui se


charge de la gestion de l’application.
/* Inclusion du module electron */
const {app, BrowserWindow} = require('electron');
function createWindow () {
// Crée une nouvelle fenêtre à la bonne taille
win=new BrowserWindow({width: 300, height: 300});
// Charge index.html comme point de départ
win.loadFile('index.html');
}

Créez un fichier index.html qui sert de point de départ à l’affichage.


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Cours du Bitcoin</title>
<link rel="stylesheet" href="css.css"></link>
</head>
<body>
<h1>Cours du Bitcoin</h1>
<div id="bitcoin">Chargement...</div>
<div id="notice">Cours issus du service cryptocompare.com</div>
</body>
</html>

26.1.3 Exécution de l’application en mode test

Grâce au module Electron et à la configuration du fichier


package.json, le lancement en mode test de l’application se fait
simplement avec la commande :
npm start

La fenêtre suivante s’affiche sur un Mac :

Nous allons maintenant reprendre notre exemple d’affichage du


cours du Bitcoin pour en faire un exécutable.
Ajoutons dans index.html les appels AJAX au service externe (voir
le chapitre sur AJAX).
Retrouvez le code source complet à l’adresse http://tjs.ovh/electron.

26.1.4 Compilation de l’application

Installons le module electron-packager, qui va compiler votre code


source en exécutable :
npm install electron-packager -g –save-dev
En vous plaçant toujours dans le répertoire de votre application,
tapez la commande :
electron-packager .

Le résultat est stocké dans un sous-répertoire du projet.


Il est nettement plus simple de compiler pour un environnement
donné depuis le même environnement. C’est-à-dire que, pour
compiler vers une application macOS, il faut utiliser un ordinateur
Apple et, pour une compilation vers un .exe, il faut avoir un poste
Windows.
À l’exécution, le fichier electron-app.exe ouvre la fenêtre :

Electron permet aussi la compilation signée de votre application.


Les notions de signature et de certificats nécessaires pour la
diffusion dans les boutiques officielles d’applications dépassent
largement le cadre de ce livre.

_ 26.2 CRÉATION D’APPLICATIONS


MOBILES
Après les applications pour bureau, abordons la création
d’applications pour mobiles. Pour le moment, Electron ne propose
pas de compilation vers les appareils mobiles. Il faut s’orienter vers
une autre solution. Si Cordova (https://cordova.apache.org) est
largement utilisé, nous allons plutôt travailler avec React Native
(https://facebook.github.io/react-native).

26.2.1 Installation de React Native

L’avantage de React Native par rapport à d’autres solutions comme


Cordova est qu’il produit des applications réellement natives et non
pas des applications hybrides qui ne font qu’embarquer un
composant web-view. Les applications créées ne sont pas
différentes d’une application créée avec les langages Objective-C
(pour iOS) ou Java (pour Android).
Comme d’habitude, l’installation du framework est réalisée en une
seule commande :
npm install -g create-react-native-app

Créez la structure de votre projet mobile avec la ligne de


commande :
create-react-native-app mobile

Placez-vous dans le répertoire mobile et lancez le serveur


cd mobile && npm start

Un menu apparaît avec différentes possibilités d’exécution.


Choisissez l’option « s » pour envoyer un mail avec l’adresse de
votre application à lancer sur votre smartphone en mode test.

26.2.2 Exécution de votre application en mode test

Grâce au serveur que vous venez de lancer sur votre poste de


travail, vous allez pouvoir tester votre application en temps réel.
Installez l’application expo sur votre appareil mobile iPhone ou
Android.
Vérifiez que votre poste de travail et votre smartphone sont bien
connectés sur le même réseau local.
26.2.3 Édition de votre application

Les applications React Native sont très proches des applications


React (voir ici). Mais, comme React Native compile des applications
natives, les éléments HTML div et span sont remplacés par les
éléments View et Text.

◆ Création d’un composant Image

En React Native, il n’est pas possible d’utiliser l’élément HTML


image pour afficher une image. Créez le fichier Logo.js contenant :
import React, { Component } from 'react';
import { AppRegistry, Image } from 'react-native';
export default class Logo extends Component {
render() {
let pic = {
uri: "https://www.toutjavascript.com/gif/bitcoin.png"
};
return (
<Image source={pic} style={{width: 100, height: 100}}/>
);
}
}

◆ Création d’un composant Bitcoin

Le composant Bitcoin est pratiquement équivalent à celui développé


ici : si vous maîtrisez React, vous pouvez créer des applications
natives très facilement.

◆ Mise à jour de votre application

Ouvrez le fichier App.js de votre projet. Chaque modification est


automatiquement répercutée sur votre application et visible dans
votre smartphone.
Dans l’en-tête du fichier, il faut importer les composants. Le
composant Logo est ensuite affiché avec la balise <Logo /> :
import React from "react";
import { StyleSheet, Text, View } from "react-native";
import Logo from "./Logo";
import Bitcoin from "./Bitcoin";

/* Page principale de l'application */


export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>Hello Bitcoin</Text>
<Logo />
<Bitcoin />
</View>
);
}
}

/* Style CSS de mise en forme de l'écran */


const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});

Retrouvez l’ensemble des sources à la page http://tjs.ovh/native.


Votre application ressemble à :
26.2.4 Compilation de votre application

La compilation d’applications mobiles sort du cadre de ce livre : elle


est souvent assez fastidieuse et les procédures changent
régulièrement.

Notez que pour créer une application iOS, un ordinateur macOS


est indispensable, même avec React Native.
Annexe CSS

Même si les feuilles de styles CSS ne font pas directement


partie de l’objet de ce livre, elles sont réellement
indispensables pour réaliser des applications modernes et
ergonomiques. Elles sont même parfois des alternatives utiles
et compactes du JavaScript. Dans cette annexe, nous allons
étudier quelques possibilités des CSS utilisées dans les
exemples des chapitres précédents.

_ UTILISATION DE FONTAWESOME
La bibliothèque FontAwesome (littéralement « police de caractères
extraordinaire ») est un outil gratuit et très puissant d’affichage de
centaines d’icônes de grande qualité sur vos pages.
L’installation se fait en copiant une seule ligne d’un appel d’une
feuille de style externe, hébergé sur un CDN (voir définition) :
<link rel="stylesheet"
href="https://use.fontawesome.com/releases/v5.0.13/css/all.css
crossorigin="anonymous">

Si vous préférez héberger les fichiers sur votre serveur pour éviter
d’éventuelles pannes du CDN, c’est possible également.
Ensuite, l’affichage d’une icône se fait avec la syntaxe suivante :
<i class="fas fa-trophy"></i>

La balise <i> (choisie par le développeur pour sa compacité par


rapport à une balise <span> par exemple) reçoit la classe fas (pour
FontAweSome) et une classe qui définit l’icône à afficher. Ici, fa-
trophy représente une coupe :

Les icônes sont dessinées au format vectoriel et sont donc toujours


nettes, même avec une grande taille de police.
Retrouvez toutes les icônes disponibles, les mises à jour, la version
téléchargeable et la version professionnelle à cette adresse
https://fontawesome.com.
La bibliothèque FontAwesome est utilisée dans les exemples de
scripts ici et là.

_ LA PSEUDO-CLASSE :HOVER
L’ajout de la pseudo-classe :hover sur un bloc de propriétés CSS
permet de détecter le survol de la souris et de modifier l’apparence
de l’élément. Ce bloc CSS change la couleur de police et de fond
lors du survol de l’élément div#survol :
div#survol:hover {
background-color:#000;
color:#fff;
}

_ LES PSEUDO-CLASSES :FIRST-CHILD


ET :LAST-CHILD
Les pseudo-classes : first-child et :last-child identifient le premier et
le dernier élément d’un groupe d’éléments, afin de leur affecter des
propriétés CSS particulières. Par exemple, nous pouvons définir des
bordures différentes pour le premier et le dernier élément d’une liste
d’éléments li :
ul.groupe li {
display:inline-block;
padding:5px;
margin:0px;
border-top:1px solid #000;
border-bottom:1px solid #000;
border-left:1px solid #000;
}
ul.groupe li:first-child {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
ul.groupe li:last-child {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
border-right:1px solid #000;
}

Avec le code HTML :


<ul class="groupe"><li>Football</li><li>Handball</li><li>Rugby</li> <li>Aqua-
poney</li><li>Bière-pong</li></ul>

La liste présente des bordures arrondies sur le premier et le dernier


élément :

_ LES PSEUDO-ÉLÉMENTS ::BEFORE


ET ::AFTER
Le CSS permet aussi d’enrichir la mise en forme d’éléments avec
des styles et même du nouveau contenu, grâce aux pseudo-
éléments ::before et :after.
Il est ainsi possible d’ajouter une image sur tous les liens ayant un
attribut target renseigné avec :
a[target]::after {
content:url(media/url_icon.gif);
padding-left:5px;
}

La propriété content: définit le contenu à créer. Ici une image


indiquant que le lien s’ouvre dans une nouvelle fenêtre :

L’explorateur d’éléments de la console du navigateur montre que le


nouvel élément ::after est bien présent dans le DOM :

Au lieu d’utiliser une image externe classique, nous pourrions utiliser


une icône de FontAwesome. La syntaxe est un peu différente,
puisqu’il faut indiquer dans la propriété content le code caractère
Unicode hexadécimal de l’icône (indiqué sur le site officiel), précédé
du caractère antislash :
a.external::after {
content:"\f35d";
font-family: "Font Awesome 5 Free";
font-weight:900;
padding-left:5px;
display: inline-block;
color:#aaa;
}

Le rendu dans le navigateur des liens externes devient alors :


Notez que la syntaxe avec un seul caractère deux-points
fonctionne également, mais la norme CSS3 recommande
d’utiliser les doubles deux-points pour distinguer les pseudo-
éléments des pseudo-classes.

_ LES BOÎTES FLÉCHÉES AVEC CSS


Les pseudo-éléments permettent également de créer sur un bloc
une sorte de flèche qui peut pointer vers l’élément de source :

La création d’une flèche avec du CSS peut paraître surprenante,


mais il s’agit en fait d’utiliser le fonctionnement de la propriété
border, qui génère automatiquement des triangles :
div#pointe {
display: inline-block;
border-width: 100px;
border-style: solid;
border-left-color: #00f; /* A gauche : bleu */
border-top-color: #fff; /* En haut : blanc */
border-right-color: #f00; /* A droite : rouge */
border-bottom-color: #000; /* En bas : noir */
}

Le bloc div#pointe se présentera ainsi à l’écran :

En utilisant le pseudo-élément ::before, avec une seule bordure côté


bas et bien positionnée par rapport à l’élément principal, nous
pouvons créer une flèche vers le haut. En ajoutant un autre élément
::after, légèrement plus petit, nous pouvons créer un effet de bordure
sur cette pointe. Reportez-vous à l’exemple d’affichage d’info-bulles
présenté ici de ce livre et à l’adresse http://tjs.ovh/infobulle.

_ LES ANIMATIONS CSS


Les animations

Nous avons déjà vu les animations CSS dans le chapitre de


manipulation du document. Pour rappel, une animation est définie
par un bloc @keyframes :
@keyframes nomKeyFrames {
from { /* Propriétés CSS de départ */ }
to { /* Propriétés CSS d’arrivée */ }
}

et par la propriété CSS raccourcie animation dans une classe :


animation: nomKeyFrames duree nbRepetition

Les transitions

Une transition est une animation simple de propriétés CSS. Des


propriétés préfixées par transition- définissent la manière dont la
transition se réalise :
transition-property définit les propriétés concernées par
l’animation, séparées par des virgules.
transition-duration définit le temps que prend la transition.
transition-delay définit le délai avant que la transition ne
démarre. Le délai est nul par défaut.
transition-timing-function définit la fonction d’accélération du
temps de la transition.
Reprenons notre liste de blocs li dans ul#groupe et ajoutons une
transition sur la couleur de fond et la couleur de texte au survol de la
souris :
ul.groupe li {
background-color:#fff;
color:#000;
transition-property: background-color, color;
transition-duration: 0.5s, 0.5s;
}
ul.groupe li:hover {
background-color:#000;
color:#fff;
}

Au survol de la souris, les propriétés background-color et color


entrent en phase de transition. Quand le pointeur souris sort de
l’élément, la transition inverse se déclenche.
Comme pour les animations, les événements JavaScript
ontransitionrun, ontransitionstart et ontransitionend détectent l’état
d’avancement de la transition.

_ LES TRANSFORMATIONS CSS


Les transformations CSS modifient les coordonnées de l’élément et
permettent des effets de rotation, de translation, d’échelle et de
perspective. Voici quelques exemples de la propriété CSS
transform: :
translate(50px, -10px) translate l’élément de 50px vers la droite
et 10px vers le haut.
rotate(0.5turn) tourne l’élément d’un demi-tour dans le sens des
aiguilles d’une montre.
scale(2) grossit l’élément par 2.
skew(30deg, 30deg) donne un effet 3D d’inclinaison de
30 degrés.
matrix(a, b, c, d, X, Y) affecte la matrice de transformation et
une translation X et Y.
rotate3d(x,y,z,angle) effectue une rotation de angle degrés
autour de l’axe 3D (x,y,z).
La propriété transform-origin: indique les coordonnées du repère
d’origine de la transformation. Voici quelques exemples de point
d’origine représenté par un cercle :

Les transformations sont également utilisables avec des animations.


Définissons un bloc @keyframes de rotation d’un tour complet :
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

Pour une rotation à vitesse constante, la propriété animation vaut :


animation: rotate 1s linear infinite;

Une rotation alternée se définit avec :


animation: rotate 1s ease-in-out alternate infinite;

Retrouvez d’autres informations utiles sur les CSS dans


l’exemple en ligne http://tjs.ovh/css.
Index

-- 1
-= 1
!1
!= 1
!== 1
? 1, 2
/* */ 1
// 1
\1
+1
++ 1
+= 1, 2
<> 1
== 1
=== 1
=> 1
2FA 1
3G 1
$1
$1 1
$_FILES 1
$_GET 1
$_POST 1
$_REQUEST 1
::after 1
::before 1
:first-child 1
:hover 1
@keyframes 1
:last-child 1
@media 1
\n 1
accept 1
Access-Control-Allow-Origin 1
action 1
Adblock 1
addColorStop() 1
addEventListener() 1
addRange() 1
adresse IP 1, 2, 3
en JavaScript 1
AJAX 1, 2
ajax() 1
aléatoire 1
alert() 1, 2
amCharts 1
Angular 1
animate() 1
animation 1
animation CSS 1
AnimationEvent 1
antislash 1
API 1
appendChild() 1
append() 1
application native 1
apply() 1
arc() 1
arguments 1, 2
array_rand() 1
array_unique() 1
Array() 1
ASCII 1, 2
async 1, 2
asynchrone 1
attr() 1
Authentification à 2 Facteurs 1
availHeight 1
availWidth 1
Babel 1
back() 1
bande passante 1
barre
de favoris 1
de statut 1
beginPath() 1
Bézier (courbe de)
1
bezierCurveTo() 1
Bitcoin 1
blur()
window 1
boîte de dialogue 1
bookmarklet 1
Boole 1
boolean 1, 2
bot 1
boucle 1
sans fin 1
break 1, 2
Brendan Eich 1
browser 1
button 1
cache 1
cache applicatif 1
callback 1, 2
call() 1
cancelAnimationFrame() 1
Canvas 1
CanvasGradient 1
Captcha 1
caractères
de substitution 1
table ASCII 1
case 1
casse 1, 2
catch 1
CDN 1, 2
chaîne
casse 1
concaténation 1
de caractères 1
longueur 1
sur plusieurs lignes 1
vide 1
champ
caché 1
texte 1
charAt() 1
charCodeAt() 1
charset 1
checkbox 1
checked 1
childNodes 1
chrome 1
classe 1, 2, 3
classList 1
className 1
clavier 1
clearInterval() 1, 2
clearRect() 1
clearTimeout() 1
clearWatch() 1
client/serveur 1
clipboard 1
ClipboardEvent 1
closed 1
close() 1
closePath() 1
code mort 1
collapsed 1
collapse() 1, 2
coloration syntaxique 1
colorDepth 1
commentaires 1
commonAncestorContainer 1
complétion automatique 1
componentDidMount() 1
componentDidUpdate() 1
compression 1
concaténation 1, 2
concat() 1
concurrente
modification 1
condition 1
console 1, 2
.table() 1
css 1
const 1
constante 1
constructeur 1
constructor() 1, 2
content-security-policy 1
content: 1
contentWindow 1
conversion 1
cookie 1
cookieEnabled 1
cookies 1
tiers 1
coords 1
Cordova 1
cos() 1
courbe
d’apprentissage 1
de Bézier 1
createElement() 1
createLinearGradient() 1
create() 1
createObjectURL() 1
createPattern() 1
createRadialGradient() 1
createRange() 1
Cross-Site Scripting 1
crypto-monnaie 1, 2
CSS 1
animations 1
sélecteur 1
transformations 1
transitions 1
css() 1
CSSStyleDeclaration 1
currentTarget 1
D3.js 1
dataset 1
dataTransfer 1, 2
Date 1, 2
Date() 1
DateTimeFormat() 1
Déclaration 1
decodeURIComponent() 1
default 1
defer 1
défilement 1
defineProperty() 1
dégradé de couleurs 1
delete 1
deleteContents() 1
déprécié 1
dessin 1
DHTML 1
différence 1
Disable JavaScript 1
disabled 1
dispatchEvent() 1
doctor
npm 1
document 1
DOM 1
DOMContentLoaded 1
DOMRect 1
DOMStringMap 1
DOMTokenList 1
Douglas Crockford 1
download 1, 2
drag and drop 1, 2
DragEvent 1
draggable 1
drawImage() 1
drop 1
Dygraphs 1
échappement 1
ECMA 1
ECMAScript 1, 2
écran 1
éditeur de texte 1
égalité 1
Electron 1
elements 1
else 1
else if 1, 2
enableHighAccuracy 1
encodeURIComponent() 1, 2
enctype 1
endContainer 1, 2
endOffset 1
enumerable 1
environnement 1
Error 1
ErrorEvent 1
escape() 1
eval() 1
événement 1
Event
UIEvent 1
every() 1
exception 1
execCommand() 1
exec() 1
Express 1
expression 1
parenthésée 1
rationnelle 1
régulière 1
extends 1, 2
extension 1, 2
false 1
favoris 1
feuilles de styles 1
Fibonacci 1
fichier 1
file 1, 2, 3
file\:// 1
FileList 1
fill() 1
fillRect() 1
fillText() 1
filter() 1
firstChild 1
focus()
window 1
fonction 1
anonyme 1
auto-exécutée 1
callback 1, 2
description 1
fléchée 1, 2
native 1
personnalisée 1
font 1
FontAwesome 1, 2
for … in 1
forEach() 1, 2
format()
date 1
nombre 1
FormData 1, 2
forward() 1
frames 1
framework 1
Free 1
fromCharCode() 1
function 1
géolocalisation 1
geolocation 1
get 1
getAllResponseHeaders() 1
getAttribute() 1
getBoundingClientRect() 1
getComputedStyle() 1
getContext() 1
getCookie() 1
getDate() 1
getDay() 1
getElementById() 1
getElementsByClassName() 1
getElementsByName() 1
getElementsByTagName() 1
getFullYear() 1
getHours() 1
getItem() 1
getManifest() 1
getMessage() 1
getMilliseconds() 1
getMinutes() 1
getMonth() 1
getOwnPropertyNames() 1
getPrototypeOf() 1
getRangeAt() 1
getSeconds() 1
getSelection() 1
getTime() 1
getTimezoneOffset() 1
getUTC 1
Git 1
GitHub 1
glisser-déposer 1, 2
gmaps.js 1
go() 1
Google
Authenticator 1
Maps JS API 1
PageSpeed 1
reCAPTCHA 1
gradient de couleurs 1
hardwareConcurrency 1
hasAttribute() 1
hashage 1
height
HTMLElement 1
screen 1
Hello World 1
help
npm 1
héritage 1
hidden 1
history 1
hostname 1
href
location 1
HTMLCanvasElement 1
HTMLCollection 1
HTMLElement 1
attributs 1
HTMLFormElement 1
htm() 1
HTTP 1
https 1
HTTP/2 1
i18n 1
if 1
iframe 1
IIFE 1
Image 1
impression 1
incrémentation 1
Indentation 1
indexOf()
tableau 1
indexOf() 1
Infinity 1
info-bulle 1, 2
init
npm 1
innerHTML 1
innerText 1
input 1
insertBefore() 1
insertNode() 1
inspecteur 1
install
npm 1, 2
instance 1
internationalisation 1
Intl 1, 2
iPhone 1, 2
iso-8859-1 1
itération 1
Java 1, 2
JavaScript 1
jBox 1
jQuery 1
JScript 1
JSON 1
tableau 1
JSX
pour React 1
KeyboardEvent 1
keyframes 1
key() 1
keys() 1
label 1
landscape 1
lastChild 1
lastIndexOf()
tableau 1
lastIndexOf() 1
length
tableau 1
lengthComputable 1
let 1
linéarisation 1
lineTo() 1
lineWidth 1
liste déroulante 1
livraison 1
local 1
localStorage 1
location 1
log() 1, 2
logo officiel 1
ls
npm 1
majuscule 1
manifest.json 1
map() 1
Maps 1
Markdown 1
match() 1
Math 1, 2
mesureText() 1
method 1, 2
méthode 1
middleware 1
MIME 1
minage 1
minification 1, 2
minuscule 1
minuteur 1
modale 1
mot de passe 1
robustesse 1
mot réservé 1
motif 1
MouseEvent 1
moveBy() 1
moveTo() 1, 2
NaN 1, 2
Native Advertising 1
navigateur 1
navigator 1
Netscape 1
Network 1
new 1
nextSibling 1
Node.js 1
NodeList 1
nombre
aléatoire 1
formatage 1
infini 1
scientifique 1
noscript 1
notation pointée 1, 2
notification 1, 2
npm 1
null 1
number 1, 2
NumberFormat 1
obfuscation 1
onblur 1
onchange 1, 2
onclick 1, 2, 3
oncopy 1
oncut 1
onDOMContentLoaded 1
onerror 1
onfocus 1
onLine 1
onload 1, 2
AJAX 1
onmessage 1
on() 1
onmouse 1
onmousemove 1
onpaste 1
onprogress 1
onreadystatechange 1, 2
onresize 1
onselect 1
onsubmit 1
ontimeout 1
ontransition 1
OOP 1
open()
AJAX 1
opener 1
open() 1
option 1
options 1
orientation 1
origin 1
outerHTML 1
PageSpeed 1, 2
parentElement 1
parseFloat() 1
parseInt() 1, 2
parse() 1
password 1
performance
mesure 1
permission 1
phishing 1
placeholder 1
platform 1
point-virgule 1
polyfill 1
POO 1
pop-up 1
pop() 1
portée 1, 2
portrait 1
Position 1
postMessage() 1
posX 1
posY 1
pratiques
bonnes 1
préchargement 1
preload 1
presse-papiers 1
preventDefault() 1, 2, 3
previousSibling 1
print() 1
private 1
production 1
programmation
événementielle 1
séquentielle 1
prompt() 1
propriété 1
protocol 1
Prototype.js 1
pseudo-classe 1
pseudo-élément 1
public 1
push() 1
querySelectorAll() 1
querySelector() 1
random() 1
Range 1
rangeCount 1
ransomware 1
React 1
React Developer Tools 1
React.js 1
ReactJS 1
ready 1
readyState 1, 2
reCAPTCHA 1
recette 1
rect() 1
récursive
fonction 1
reduce() 1
reduceRight() 1
référencement 1, 2, 3, 4
RegExp() 1
reload() 1
removeAllRanges() 1
removeChild() 1
removeEventListener() 1
removeItem() 1
render()
node.js 1
replace()
location 1
replace() 1
requestAnimationFrame() 1
requestPermission() 1
required 1
require() 1
resizeBy() 1
resizeTo() 1
response 1
responsive 1
responsive design
canvas 1
restore() 1
retargeting 1, 2
retour à la ligne 1
return 1
reverse() 1
RGPD 1, 2
rotate() 1
runtime 1
same-origin policy 1
save() 1
screen 1
screenLeft 1
screenTop 1
screenX 1
screenY 1
scrollBy() 1
scrollTo() 1
search 1, 2
search() 1
sécurité 1
select 1
selected 1
selectedIndex 1
Selection 1
select() 1
selectNodeContents() 1
sendMessage(). 1
send() 1
séparateur 1
'1
Serveur DNS 1
sessionStorage 1
setAttribute() 1
setCookie() 1, 2
setEnd() 1
setInterval() 1, 2
setItem() 1
setLineDash() 1
setRequestHeader() 1
setStart() 1
setTime() 1
setTimeout() 1
setTransform() 1
shift() 1
shuffle() 1
sin() 1
site-under 1
slice() 1
smartphone 1
some() 1
sort() 1
souris 1
spellcheck 1
splice() 1
split() 1
src 1
startContainer 1
startOffset 1
static
node.js 1
status 1, 2
stopPropagation() 1
Storage 1
strict 1
string 1
stringify() 1
stroke() 1
strokeStyle 1
strokeText() 1
substring() 1
substr() 1
super() 1
supportedLocalesOf() 1
surroundContents() 1
SVN 1
switch 1
synchrone 1
tableau 1
associatif 1
conversion 1
multidimensionnel 1
table() 1
target 1
taux de rebond 1
taux de transformation 1
télécharger 1
template 1
terminate() 1
test unitaire 1
test() 1
textarea 1, 2
textBaseline 1
this 1, 2, 3
three.js 1
throw 1
timeEnd() 1
time() 1
timeout 1
timestamp 1
toDataURL() 1
toFixed() 1
toLowerCase() 1
toString() 1
toUpperCase() 1
tracking 1
traduction 1
transform-origin: 1
transform: 1
transition 1
transition- 1
Trends
Google 1
trigonométrique 1
trim() 1
true 1
try 1
tsc 1
Twig 1
typeof 1, 2
TypeScript 1
undefined 1
underscore 1
unescape() 1
upload 1, 2
use strict 1
use
npm 1
userAgent 1
UTF-8 1
V8 1
value 1
Vanilla JS 1
var 1
variable 1
globale 1
locale 1
portée 1
vie privée 1
view
npm 1
vis.js 1
void 1
watchPosition() 1
Web Developer 1, 2
Web Worker 1
WebSocket 1, 2
WheelEvent 1
which 1
width
HTMLElement 1
screen 1
window 1
Worker 1
X-Frame-Options 1
XML 1
XMLHttpRequest 1
XSS 1
1. Estimations d’Internet World Stats.
1. Source : https://www.publicisregicom.fr/investissements-publicitaires-2017-internet-
tete/
1. Source : http://gs.statcounter.com/#browser-ww-daily-20120509-20120517-bar
This le was downloaded from Z-Library project

Your gateway to knowledge and culture. Accessible for everyone.

z-library.se singlelogin.re go-to-zlibrary.se single-login.ru

O cial Telegram channel

Z-Access

https://wikipedia.org/wiki/Z-Library
ffi
fi

Vous aimerez peut-être aussi