Découvrez Le Langage JavaScript - OpenClassrooms Eyrolles
Découvrez Le Langage JavaScript - OpenClassrooms Eyrolles
Découvrez Le Langage JavaScript - OpenClassrooms Eyrolles
QU’ALLEZ-VOUS APPRENDRE ?
• Premiers pas en JavaScript
• Les variables et les conditions
• Les boucles et les fonctions
• Les objets et les tableaux
• Déboguer le code
• TP : convertir un nombre en toutes lettres
JavaScript et HTML 5
• L’audio et la vidéo
• L’élément Canvas
• L’API File et le drag & drop
L’ESPRIT D’OPENCLASSROOMS
Des cours ouverts, riches et vivants, conçus pour tous les niveaux et accessibles à
tous gratuitement sur notre plate-forme d’e-éducation : www.openclassrooms.com.
Vous y vivrez une véritable expérience communautaire de l’apprentissage, permettant
à chacun d’apprendre avec le soutien et l’aide des autres étudiants sur les forums.
Vous profiterez des cours disponibles partout, tout le temps : sur le Web, en PDF, en
eBook, en vidéo…
Biographie auteur
À PROPOS DES AUTEURS
Sébastien de la Marck, passionné des technologies du Web et plus particulièrement du
JavaScript, est l’auteur de plusieurs programmes développés avec ce langage. Il
considère que le JavaScript doit être connu des webmasters, en plus du trio HTML/CSS
et PHP.
Johann Pardanaud, étudiant et féru d’informatique, découvre les joies de la
programmation à la fin de ses années de collège. Très vite, il se lance dans le
développement web et tombe sur son langage de prédilection, le JavaScript, qu’il
décide d’enseigner via OpenClassrooms.
www.editions-eyrolles.com
DANS LA MÊME COLLECTION
Découvrez le LANGAGE
JavaScript
ÉDITIONS EYROLLES
61, bd Saint-Germain
75240 Paris Cedex
www.editions-eyrolles.com
Attention : la version originale de cet ebook est en couleur, lire ce livre numérique sur un support de
lecture noir et blanc peut en réduire la pertinence et la compréhension.
Attention : pour lire les exemples de lignes de code, réduisez la police de votre support au maximum.
Structure de l’ouvrage
Le plan de ce livre a été conçu pour faciliter votre apprentissage du JavaScript. Voici le
chemin que nous allons parcourir :
• Les bases du JavaScript : cette partie sera consacrée à l’apprentissage des bases du
langage. Nous passerons en revue tout ce qui concerne les particularités du langage
et nous reprendrons toutes les notions de programmation afin que les débutants en
programmation ne soient pas perdus. À la fin de cette partie, vous serez invités à
réaliser une application capable d’afficher un nombre en toutes lettres ; elle
réutilisera l’ensemble des notions que vous aurez acquises et mettra votre capacité
de réflexion à rude épreuve.
• Modeler les pages web : après une première partie consacrée aux bases, vous serez
ici plongés dans la modélisation de pages web « dynamiques ». Le dynamisme est
une notion importante du JavaScript, c’est cela qui permet à l’utilisateur d’interagir
avec votre page. Vous découvrirez aussi comment modifier le contenu d’une page
web, créer et manipuler des événements et modifier le style CSS de vos éléments
HTML. Comme dans la première partie, un TP est destiné à vous faire travailler sur
ce que vous aurez appris au cours de votre lecture.
• Les objets et les design patterns : une fois les connaissances nécessaires en termes
d’interaction avec une page web et les utilisateurs acquises, vous pourrez alors vous
investir dans une utilisation un peu plus avancée du JavaScript. Vous y découvrirez
comment créer des objets évolués et quels sont les objets natifs les plus intéressants
à manipuler. Vous serez aussi initiés au traitement avancé des chaînes de caractères,
des nombres et des tableaux, et vous apprendrez à gérer le temps et les images.
• L’échange de données avec Ajax : nous aborderons ici la notion d’échange de
données entre un client et un serveur et étudierons les diverses possibilités qui
s’offrent à nous afin de faire communiquer nos scripts avec un serveur sans pour
autant devoir recharger la page web. Un point sera fait sur les structures de données
afin que vous sachiez sous quelle forme transférer vos données et vous découvrirez
comment réaliser un système d’upload de fichier. La partie se terminera avec la
création d’un système d’autocomplétion, chose qui vous resservira très certainement.
• JavaScript et HTML 5 : vous avez maintenant terminé votre apprentissage sur tout
ce qui était essentiel au JavaScript, dans cette partie nous irons donc un peu plus loin
et explorerons la manière dont le HTML 5 permet au JavaScript d’aller encore plus
loin. Nous ferons avant tout un point sur ce qu’apporte le HTML 5 par rapport au
JavaScript puis nous nous pencherons sur quelques points essentiels, à savoir : la
manipulation des balises < audio >, <video > et < canvas >, la gestion des
fichiers et la mise en place d’un système de Drag & Drop.
• Annexes : vous trouverez ici de quoi approfondir vos connaissances en termes de
débogage de code et vous découvrirez les closures qui, bien qu’un brin complexes,
vous apporteront un confort appréciable dans votre manière de développer. L’ultime
chapitre vous donnera un récapitulatif de certains points essentiels du JavaScript et
vous montrera que ce langage peut se rendre très utile même en dehors des
navigateurs web.
Comment lire ce livre ?
Suivez l’ordre des chapitres
Lisez ce livre comme on lit un roman. Il a été conçu pour cela.
Contrairement à beaucoup de livres techniques où il est courant de lire en diagonale et
de sauter certains chapitres, il est ici fortement recommandé de suivre l’ordre du livre,
à moins que vous ne soyez déjà un peu expérimenté.
http://odyssey.sdlm.be/javascript.htm
Nous tenons à remercier les personnes qui ont contribué de près ou de loin à l’écriture
de ce cours. Sans elles, ce cours aurait eu du mal à voir le jour !
Commençons par trois accros du JavaScript :
Yann Logan – Golmote (https://openclassrooms.com/membres/golmote-13169) –, pour
ses relectures et ses conseils avisés.
Xavier Montillet – xavierm02 (https://openclassrooms.com/membres/xavierm02-
20626) –, pour ses remarques sur tout ce qui nous semblait négligeable mais qui
importait beaucoup au final !
Benoît Mariat – restimel (https://openclassrooms.com/membres/restimel-61309) –, qui
a activement participé aux débuts quelque peu chaotiques du cours.
Merci encore à vous trois, on se demande toujours ce qu’on aurait fait sans vous !
S’ensuivent certaines personnes d’OpenClassrooms :
Jonathan Baudoin – John-John (https://openclassrooms.com/membres/john-john-
35734) –, qui a supporté nos coups de flemme pendant plus d’un an !
Pierre Dubuc – karamilo (https://openclassrooms.com/membres/karamilo-40796) –,
pour nous avoir lancés dans ce projet immense et nous avoir aidés à mettre en place les
premiers chapitres du cours.
Mathieu Nebra – M@teo21 (https://openclassrooms.com/membres/mateo21) –, sans
qui ce cours n’aurait jamais vu le jour puisqu’il est le créateur du Site du Zéro
OpenClassrooms.
Merci aussi à nos familles respectives, qui nous ont encouragés pendant plus d’un an et
demi !
Merci à vous tous !
Table des matières
3 Les variables
Qu’est-ce qu’une variable ?
Déclarer une variable
Les types de variables
Tester l’existence de variables avec typeof
Les opérateurs arithmétiques
Quelques calculs simples
Simplifier encore plus les calculs
Initiation à la concaténation et à la conversion des types
La concaténation
Interagir avec l’utilisateur
Convertir une chaîne de caractères en nombre
Convertir un nombre en chaîne de caractères
En résumé
4 Les conditions
La base de toute condition : les booléens
Les opérateurs de comparaison
Les opérateurs logiques
L’opérateur ET
L’opérateur OU
L’opérateur NON
Combiner les opérateurs
La condition if else
La structure if pour dire « si »
Petit intermède : la fonction confirm()
La structure else pour dire « sinon »
La structure else if pour dire « sinon si »
La condition switch
Les ternaires
Les conditions sur les variables
Tester l’existence de contenu d’une variable
Le cas de l’opérateur OU
Un petit exercice pour la forme !
Présentation de l’exercice
Correction
En résumé
5 Les boucles
L’incrémentation
Le fonctionnement
L’ordre des opérateurs
La boucle while
Répéter tant que…
Exemple pratique
Quelques améliorations
La boucle do while
La boucle for
for, la boucle conçue pour l’incrémentation
Reprenons notre exemple
Portée des variables de boucles
Priorité d’exécution
En résumé
6 Les fonctions
Concevoir des fonctions
Créer sa première fonction
Un exemple concret
La portée des variables
La portée des variables
Les variables globales
Les variables globales ? Avec modération !
Les arguments et les valeurs de retour
Les arguments
Les fonctions anonymes
Les fonctions anonymes : les bases
Retour sur l’utilisation des points-virgules
Les fonctions anonymes : isoler son code
En résumé
7 Les objets et les tableaux
Introduction aux objets
Que contiennent les objets ?
Le constructeur
Les propriétés
Les méthodes
Exemple d’utilisation
Objets natifs déjà rencontrés
Les tableaux
Les indices
Déclarer un tableau
Récupérer et modifier des valeurs
Opérations sur les tableaux
Ajouter et supprimer des items
Chaînes de caractères et tableaux
Parcourir un tableau
Parcourir avec for
Attention à la condition
Les objets littéraux
La syntaxe d’un objet
Accès aux items
Ajouter des items
Parcourir un objet avec for in
Utilisation des objets littéraux
Exercice récapitulatif
Énoncé
Correction
En résumé
8 Déboguer le code
En quoi consiste le débogage ?
Les bogues
Le débogage
Les kits de développement et leur console
Aller plus loin avec la console
Utiliser les points d’arrêt
Les points d’arrêt
La pile d’exécution
En résumé
9 TP : convertir un nombre en toutes lettres
Présentation de l’exercice
La marche à suivre
L’orthographe des nombres
Tester et convertir les nombres
Retour sur la fonction parseInt()
La fonction isNaN()
Il est temps de se lancer !
Correction
Le corrigé complet
Les explications
Conclusion
12 Les événements
Que sont les événements ?
La théorie
Le focus
La pratique
Le mot-clé this
Retour sur le focus
Bloquer l’action par défaut de certains événements
L’utilisation de javascript: dans les liens
Les événements au travers du DOM
Le DOM-0
Le DOM-2
Le DOM-2
Les phases de capture et de bouillonnement
L’objet Event
Généralités sur l’objet Event
Les fonctionnalités de l’objet Event
Résoudre les problèmes d’héritage des événements
Le problème
La solution
En résumé
13 Les formulaires
Les propriétés
Un classique : value
Les booléens avec disabled, checked et readonly
Les listes déroulantes avec selectedIndex et options
Les méthodes et un retour sur quelques événements
Les méthodes spécifiques à l’élément <form>
La gestion du focus et de la sélection
Explications sur l’événement change
En résumé
14 Manipuler le CSS
Éditer les propriétés CSS
Quelques rappels sur le CSS
Éditer les styles CSS d’un élément
Récupérer les propriétés CSS
La fonction getComputedStyle()
Les propriétés de type offset
La propriété offsetParent
Votre premier script interactif !
Présentation de l’exercice
Corrigé
L’exploration du code HTML
L’ajout des événements mousedown et mouseup
La gestion du déplacement de notre élément
Empêcher la sélection du contenu des éléments déplaçables
En résumé
15 Déboguer le code
L’inspecteur web
Les règles CSS
Les points d’arrêt
En résumé
16 TP : un formulaire interactif
Présentation de l’exercice
Corrigé
La solution complète : HTML, CSS et JavaScript
Explications
La mise en place des événements : partie 1
La mise en place des événements : partie 2
22 La gestion du temps
Le système de datation
Introduction aux systèmes de datation
L’objet Date
Mise en pratique : calculer le temps d’exécution d’un script
Les fonctions temporelles
Utiliser setTimeout() et setInterval()
Annuler une action temporelle
Mise en pratique : les animations
En résumé
23 Les tableaux
L’objet Array
Le constructeur
Les propriétés
Les méthodes
Concaténer deux tableaux
Parcourir un tableau
Rechercher un élément dans un tableau
Trier un tableau
Extraire une partie d’un tableau
Remplacer une partie d’un tableau
Tester l’existence d’un tableau
Les piles et les files
Retour sur les méthodes étudiées
Les piles
Les files
Quand les performances sont absentes : unshift() et shift()
En résumé
24 Les images
L’objet Image
Le constructeur
Les propriétés
Événements
Particularités
Mise en pratique
En résumé
26 Les closures
Les variables et leurs accès
Comprendre le problème
Premier exemple
Un cas concret
Explorer les solutions
Une autre utilité, les variables statiques
En résumé
28 XMLHttpRequest
L’objet XMLHttpRequest
Présentation
XMLHttpRequest, versions 1 et 2
Première version : les bases
Préparation et envoi de la requête
Synchrone ou asynchrone ?
Transmission des paramètres
Réception des données
Requête asynchrone : spécifier la fonction de callback
Traitement des données
Récupération des en-têtes de la réponse
Mise en pratique
XHR et les tests locaux
Gestion des erreurs
Résoudre les problèmes d’encodage
L’encodage pour les débutants
Une histoire de normes
L’encodage et le développement web
L’Ajax et l’encodage des caractères
Seconde version : usage avancé
Les requêtes cross-domain
Une sécurité bien restrictive
Autoriser les requêtes cross-domain
Nouvelles propriétés et méthodes
En résumé
31 Déboguer le code
L’analyse des requêtes
Lister les requêtes
Analyser une requête
32 TP : un système d’autocomplétion
Présentation de l’exercice
Les technologies à employer
Principe de l’autocomplétion
Conception
C’est à vous !
Corrigé
Le corrigé complet
Les explications
Idées d’amélioration
34 L’audio et la vidéo
L’audio
Contrôles simples
Analyse de la lecture
Améliorations
La vidéo
En résumé
35 L’élément Canvas
Premières manipulations
Principe de fonctionnement
Le fond et les contours
Effacer
Formes géométriques
Les chemins simples
Les arcs
Utilisation de moveTo()
Les courbes de Bézier
Images et textes
Les images
Mise à l’échelle
Recadrage
Les patterns
Le texte
Lignes et dégradés
Les styles de lignes
Opérations
L’état graphique
Les translations
Les rotations
Animations
Une question de « framerate »
Un exemple concret
En résumé
36 L’API File
Première utilisation
Les objets Blob et File
L’objet Blob
L’objet File
Lire les fichiers
Mise en pratique
Upload de fichiers avec l’objet XMLHttpRequest
En résumé
Index
Première partie
Les bases du JavaScript
Avant d’entrer directement dans le vif du sujet, ce chapitre va vous apprendre ce qu’est
le JavaScript, ce qu’il permet de faire, quand il peut ou doit être utilisé et comment il a
considérablement évolué depuis sa création en 1995.
Nous aborderons aussi plusieurs notions de bases telles que les définitions exactes de
certains termes.
Un langage de programmation
Un langage de programmation est un langage qui permet aux développeurs d’écrire du
code source qui sera analysé par l’ordinateur.
Un développeur, ou un programmeur, est une personne qui développe des programmes.
Ça peut être un professionnel (un ingénieur, un informaticien ou un analyste
programmeur) ou bien un amateur.
Le code source est écrit par le développeur. C’est un ensemble d’actions, appelées
« instructions », qui vont permettre de donner des ordres à l’ordinateur afin de faire
fonctionner le programme. Le code source est quelque chose de caché, un peu comme un
moteur dans une voiture : le moteur est caché, mais il est bien là, et c’est lui qui fait en
sorte que la voiture puisse être propulsée. Dans le cas d’un programme, c’est pareil :
c’est le code source qui régit le fonctionnement du programme.
En fonction du code source, l’ordinateur exécute différentes actions, comme ouvrir un
menu, démarrer une application, effectuer une recherche, etc., soit tout ce que
l’ordinateur est capable de faire. Il existe énormément de langages de programmation
(un bon nombre d’entre eux sont listés sur la page suivante : http://fr.wikipedia.org/
wiki/Cat%C3%A9gorie%3ALangage_de_programmation).
Programmer des scripts
Le JavaScript permet de programmer des scripts. Comme nous venons de le voir, un
langage de programmation permet d’écrire du code source qui sera analysé par
l’ordinateur. Il existe trois manières d’utiliser du code source.
• Langage compilé : le code source est donné à un programme appelé « compilateur »
qui va lire le code source et le convertir dans un langage que l’ordinateur sera
capable d’interpréter : c’est le langage binaire, fait de 0 et de 1. Les langages comme
le C ou le C++ sont des langages dits compilés.
• Langage précompilé : ici, le code source est compilé partiellement, généralement
dans un code plus simple à lire pour l’ordinateur, mais qui n’est pas encore du
binaire. Ce code intermédiaire devra être lu par ce qu’on appelle une « machine
virtuelle » qui exécutera ce code. Les langages comme le C# ou le Java sont dits
précompilés.
• Langage interprété : dans ce cas, il n’y a pas de compilation. Le code source reste tel
quel, et si l’on veut l’exécuter, on doit le fournir à un interpréteur qui se chargera de
le lire et de réaliser les actions demandées. Éventuellement, pour obtenir de
significatifs gains de performances, le code peut être compilé à la volée
(http://fr.wikipedia.org/wiki/Compilation_%C3%A0_la_vol%C3%A9e) pendant son
exécution. C’est aujourd’hui ce que font la plupart des interpréteurs JavaScript.
Les scripts sont majoritairement interprétés. Et quand on dit que le JavaScript est un
langage de scripts, cela signifie qu’il s’agit d’un langage interprété ! Il est donc
nécessaire d’utiliser un interpréteur pour faire fonctionner du code JavaScript, ce que
vous faites fréquemment car il est inclus dans votre navigateur web !
En effet, chaque navigateur possède un interpréteur JavaScript propre :
• pour Internet Explorer, il s’agit de Chakra (l’interpréteur JavaScript des versions
antérieures à IE 9 est JScript) ;
• pour Mozilla Firefox, l’interpréteur se nomme SpiderMonkey ;
• pour Google Chrome, il s’agit de V8.
Un logo inconnu
Il n’y a pas de logo officiel pour représenter le JavaScript. Cependant, le logo suivant
est beaucoup utilisé par la communauté, surtout depuis sa présentation à la JSConf EU
de 2011. Vous pourrez le trouver à cette adresse :
https://github.com/voodootikigod/logo.js sous différents formats. N’hésitez pas à en
abuser en cas de besoin.
En résumé
• Le JavaScript est un langage de programmation interprété, c’est-à-dire qu’il a besoin
d’un interpréteur pour pouvoir être exécuté.
• Le JavaScript est utilisé majoritairement au sein des pages web mais son utilisation
en guise de serveur ou d’application commence à se répandre.
• Tout comme le HTML, le JavaScript est généralement exécuté par le navigateur de
l’internaute : on parle d’un comportement client-side, par opposition au server-side
lorsque le code est exécuté par le serveur.
• Le JavaScript est standardisé par l’ECMA International sous le nom d’ECMAScript,
qui constitue la référence du langage.
• La dernière version standardisée du JavaScript est basée sur l’ECMAScript 5, sorti
en 2009. Mais sa nouvelle version, ECMAScript 6, prend du terrain.
QCM
(http://odyssey.sdlm.be/javascript/01/partie1/chapitre1/qcm.htm)
2 Premiers pas en JavaScript
<script>
alert('Hello world!');
</script>
</body>
</html>
Les nouveautés
Dans le code HTML donné précédemment, on remarque quelques nouveautés.
Tout d’abord, un élément <script> est présent : c’est lui qui contient le code
JavaScript que voici :
alert('Hello world!');
Il s’agit d’une instruction, c’est-à-dire une commande, un ordre, ou plutôt une action que
l’ordinateur va devoir réaliser. Les langages de programmation sont constitués d’une
suite d’instructions qui, mises bout à bout, permettent d’obtenir un programme ou un
script complet.
Dans cet exemple, il n’y a qu’une instruction : l’appel de la fonction alert().
La syntaxe du JavaScript
Les instructions
La syntaxe du JavaScript n’est pas compliquée. De manière générale, les instructions
doivent être séparées par un point-virgule qu’on place à la fin de chaque instruction :
instruction_1;
instruction_2;
instruction_3;
En réalité le point-virgule n’est pas obligatoire si l’instruction qui suit se trouve sur la
ligne suivante, comme dans notre exemple. En revanche, si vous écrivez plusieurs
instructions sur une même ligne, comme dans l’exemple suivant, le point-virgule est
fortement recommandé. Dans le cas contraire, l’interpréteur risque de ne pas
comprendre qu’il s’agit d’une autre instruction et retournera une erreur.
Instruction_1; Instruction_2;
Instruction_3;
Vous l’aurez compris, il existe deux écoles : celle avec les points-virgules et
celle sans points-virgules. Dans ce cours, nous les utiliserons en permanence
et vous êtes fortement encouragés à faire de même.
Les espaces
Le JavaScript n’est pas sensible aux espaces. Cela veut dire que vous pouvez aligner
des instructions comme vous le voulez, sans que cela gêne en rien l’exécution du script.
Par exemple, ceci est correct :
instruction_1
instruction_1_1
instruction_1_2
instruction_2; instruction_3
Indentation et présentation
En informatique, l’indentation consiste à structurer le code pour le rendre plus lisible.
Les instructions sont hiérarchisées en plusieurs niveaux et on utilise des espaces ou des
tabulations pour les décaler vers la droite et ainsi créer une hiérarchie. Voici un
exemple de code indenté :
function toggle(elemID) {
var elem = document.getElementById(elemID);
Ce code est indenté de quatre espaces, c’est-à-dire que le décalage est chaque fois un
multiple de quatre. Un décalage de quatre espaces est courant, tout comme un décalage
de deux. Il est possible d’utiliser des tabulations pour indenter du code, qui ont
l’avantage d’être affichées différemment suivant l’éditeur utilisé. Ainsi, si vous
transmettez votre code à quelqu’un, l’indentation qu’il verra dépendra de son éditeur et
il ne sera pas perturbé par une indentation qu’il n’apprécie pas (par exemple,
indentation de deux espaces au lieu de quatre). Cependant, d’une manière générale, les
espaces sont utilisés.
Voici le même code, non indenté. Vous constatez que l’indentation est une aide à la
lecture :
function toggle(elemID) {
var elem = document.getElementById(elemID);
La présentation des codes est également importante, un peu comme si vous rédigiez une
lettre : cela ne se fait pas n’importe comment. Il existe des règles prédéfinies à
respecter pour organiser votre code de façon claire. Dans le code indenté précédent,
vous pouvez voir qu’il y a des espaces un peu partout pour aérer le code et qu’il y a une
seule instruction par ligne (à l’exception des if else, mais nous verrons cela plus
tard). Certains développeurs écrivent leur code comme ceci :
function toggle(elemID){
var elem=document.getElementById(elemID);
if(elem.style.display==='block'){
elem.style.display='none';
}else{elem.style.display='block';}
}
Vous conviendrez, comme nous, que c’est tout de suite moins lisible, non ? Gardez à
l’esprit que votre code doit être propre, même si personne d’autre n’y touche. En effet,
vous pouvez laisser le code de côté quelque temps sans travailler dessus et le reprendre
par la suite, et là, bonne chance pour vous y retrouver !
Les commentaires
Les commentaires sont des annotations ajoutées par le développeur pour expliquer le
fonctionnement d’un script, d’une instruction ou même d’un groupe d’instructions. Ils ne
gênent pas l’exécution d’un script.
Il existe deux types de commentaires : les commentaires de fin de ligne et les
commentaires multilignes.
Le texte placé dans un commentaire est ignoré lors de l’exécution du script, ce qui veut
dire que vous pouvez mettre ce que bon vous semble en commentaire, même une
instruction (qui ne sera évidemment pas exécutée) :
instruction_1; // Ceci est ma première instruction
instruction_2;
// La troisième instruction ci-dessous pose problème, je l'annule temporairement
// instruction_3;
Commentaires multilignes
Ce type de commentaire permet les retours à la ligne. Un commentaire multiligne
commence par les caractères /* et se termine par */ :
/* Ce script comporte 3 instructions :
- Instruction 1 qui fait telle chose
- Instruction 2 qui fait autre chose
- Instruction 3 qui termine le script
*/
instruction_1;
instruction_2;
instruction_3; // Fin du script
Remarquez qu’un commentaire multiligne peut aussi être affiché sur une seule ligne :
instruction_1; /* Ceci est ma première instruction */
instruction_2;
Les fonctions
Dans l’exemple du « Hello world! », nous avons utilisé la fonction alert(). Nous
reviendrons en détail sur le fonctionnement des fonctions, mais pour les chapitres
suivants, il sera nécessaire de connaître sommairement leur syntaxe.
Une fonction se compose de deux choses : son nom, suivi d’un couple de parenthèses
(une ouvrante et une fermante) :
myFunction(); // « function » veut dire « fonction » en anglais
Entre les parenthèses se trouvent les arguments, qu’on appelle aussi « paramètres ».
Ceux-ci contiennent des valeurs qui sont transmises à la fonction (pour cet exemple, les
mots « Hello world! ») :
alert('Hello world!');
Si vous n’utilisez pas le HTML 5, sachez que l’attribut type prend comme valeur
text/javascript, qui est en fait le type MIME d’un code JavaScript.
<body>
<script>
alert('Hello world!');
</script>
</body>
</html>
//-->
</script>
</body>
Le JavaScript externe
Il est possible, et même conseillé, d’écrire le code JavaScript dans un fichier externe,
portant l’extension .js. Ce fichier est ensuite appelé depuis la page web au moyen de
l’élément <script> et de son attribut src qui contient l’URL du fichier .js.
alert('Hello world!');
<!DOCTYPE html>
<html>
<head>
<title>Hello World!</title>
</head>
<body>
<script src="hello.js"></script>
</body>
</html>
On suppose ici que le fichier hello.js se trouve dans le même répertoire que la page
web.
<body>
<p>
<!--
...
-->
</p>
<script>
// Un peu de code JavaScript...
</script>
<script src="hello.js"></script>
</body>
</html>
Quelques aides
Les documentations
Pendant la lecture de ce livre, il se peut que vous ayez besoin de plus de renseignements
sur divers points abordés. Normalement, toutes les informations nécessaires sont
fournies mais vous pouvez avoir envie de vous documenter davantage. Nous vous
conseillons de consulter le Mozilla Developer Network
(https://developer.mozilla.org/fr/JavaScript/R%C3%A9f%C3%A9rence_JavaScript),
site web regroupant de nombreux documents listant tout ce qui constitue un langage de
programmation (instructions, fonctions, etc.). Généralement, tout est trié par catégories
et quelques exemples sont fournis. Mais gardez bien à l’esprit que les documentations
n’ont aucun but pédagogique, elles ont pour seule fonction de lister tout ce qui fait un
langage, sans trop s’étendre sur les explications. Donc si vous recherchez comment
utiliser une certaine fonction (comme alert()) c’est très bien. Mais ne vous attendez
pas à apprendre les bases du JavaScript grâce à ce genre de site. C’est possible, mais
suicidaire, surtout si vous débutez en programmation.
En résumé
• Les instructions doivent être séparées par un point-virgule.
• Un code JavaScript bien présenté est plus lisible et plus facilement modifiable.
• Il est possible d’inclure des commentaires au moyen des caractères //, /* et */.
• Les codes JavaScript se placent dans une balise <script>.
• Il est possible d’inclure un fichier JavaScript grâce à l’attribut src de la balise
<script>.
QCM
(http://odyssey.sdlm.be/javascript/02/partie1/chapitre2/qcm.htm)
Questionnaire didactique
(http://odyssey.sdlm.be/javascript/03/partie1/chapitre2/questionnaire.htm)
3 Les variables
Nous abordons enfin le premier chapitre technique de cet ouvrage ! Tout au long de sa
lecture, vous allez découvrir l’utilisation des variables, les différents types principaux
qu’elles peuvent contenir et surtout comment faire vos premiers calculs. Vous serez
aussi initiés à la concaténation et à la conversion des types. Et enfin, un élément
important de ce chapitre, vous allez apprendre à utiliser une nouvelle fonction qui vous
permettra d’interagir avec l’utilisateur !
Concernant les mots-clés utilisés par le JavaScript, on peut les appeler les
« mots réservés », tout simplement parce que vous n’avez pas le droit d’en
faire usage en tant que noms de variables. Vous trouverez sur cette page (en
anglais) tous les mots réservés par le JavaScript
https://developer.mozilla.org/en/JavaScript/Reference/Reserved_Words.
Le JavaScript étant un langage sensible à la casse, faites bien attention à ne pas vous
tromper sur les majuscules et les minuscules utilisées. Dans l’exemple suivant, nous
avons bel et bien trois variables différentes déclarées :
var myVariable;
var myvariable;
var MYVARIABLE;
Le mot-clé var indique que vous déclarez une variable. Une fois celle-ci déclarée, il ne
vous est plus nécessaire d’utiliser ce mot-clé pour cette variable et vous pouvez y
stocker ce que vous souhaitez :
var myVariable;
myVariable = 2;
Le signe = sert à attribuer une valeur à la variable (ici, 2). Quand on donne une valeur à
une variable, on dit qu’on fait une « affectation », car on affecte une valeur à la variable.
Il est possible de simplifier ce code en une seule ligne :
var myVariable = 5.5; // Comme vous pouvez le constater, les nombres
// à virgule s'écrivent avec un point.
De même, vous pouvez déclarer et assigner des variables sur une seule et même ligne :
var myVariable1, myVariable2 = 4, myVariable3;
Ici, nous avons déclaré trois variables en une ligne mais seulement la deuxième s’est vu
attribuer une valeur.
Quand vous utilisez une seule fois l’instruction var pour déclarer plusieurs
variables, vous devez placer une virgule après chaque variable (et son
éventuelle attribution de valeur) et vous ne devez utiliser le point-virgule (qui
termine une instruction) qu’à la fin de la déclaration de toutes les variables.
Et enfin une dernière chose qui pourra vous être utile de temps en temps :
var myVariable1, myVariable2;
myVariable1 = myVariable2 = 2;
Les deux variables contiennent maintenant le nombre 2 ! Vous pouvez faire la même
chose avec autant de variables que vous le souhaitez.
Il est important de préciser que si vous écrivez var myVariable = '2';, le type de
cette variable est une chaîne de caractères et non pas un type numérique.
Une autre précision importante : si vous utilisez les apostrophes pour « encadrer »
votre texte et que vous souhaitez utiliser des apostrophes dans ce même texte, il vous
faudra alors « échapper » vos apostrophes de cette façon :
var text = 'Ça c\'est quelque chose !';
Dans le cas contraire, JavaScript croira que votre texte s’arrête à l’apostrophe
contenue dans le mot « c’est ». À noter que ce problème est identique pour les
guillemets.
En ce qui nous concerne, nous utilisons généralement les apostrophes mais quand le
texte en contient trop, alors les guillemets peuvent être bien utiles. À vous de voir
comment vous souhaitez présenter vos codes.
• Les booléens (boolean) : les booléens sont un type bien particulier que nous
étudierons de façon plus détaillée au chapitre suivant. Pour le moment, disons qu’il
s’agit d’un type pouvant avoir deux états : vrai (true) ou faux (false). Ces deux
états s’écrivent de la façon suivante :
var isTrue = true;
var isFalse = false;
OPÉRATEUR SIGNE
addition +
soustraction -
multiplication *
division /
modulo %
Le modulo correspond au reste d’une division. Par exemple, si vous divisez 5 par 2, le
modulo vaut 1.
Vous savez faire des calculs avec deux nombres, c’est bien. Mais avec deux variables
contenant elles-mêmes des nombres, c’est mieux :
var number1 = 3, number2 = 2, result;
result = number1 * number2;
alert(result); // Affiche : « 6 »
On peut aller encore plus loin en écrivant des calculs impliquant plusieurs opérateurs
ainsi que des variables :
var divisor = 3, result1, result2, result3;
result1 = (16 + 8) / 2 - 2 ; // 10
result2 = result1 / divisor;
result3 = result1 % divisor;
Vous remarquerez que nous avons utilisé des parenthèses pour le calcul de la variable
result1. Elles s’utilisent comme en mathématiques : le navigateur calcule ainsi en
premier 16 + 8, puis divise le résultat par 2.
Ce n’est pas spécialement long ou compliqué à faire, mais cela peut devenir très vite
rébarbatif. Il existe une solution plus simple pour ajouter un nombre à une variable :
var number = 3;
number += 5;
alert(number); // Affiche : « 8 »
Ce code a exactement le même effet que le précédent mais est plus rapide à écrire.
À noter que ces instructions ne s’appliquent pas uniquement aux additions, elles
fonctionnent avec tous les autres opérateurs arithmétiques : +=, -=, *=, /= et %=.
Initiation à la concaténation et à la conversion des types
Certains opérateurs ont des particularités cachées. Prenons l’opérateur +: en plus des
additions, il permet de faire ce qu’on appelle des « concaténations » entre des chaînes
de caractères.
La concaténation
Une concaténation consiste à ajouter une chaîne de caractères à la fin d’une autre,
comme dans cet exemple :
var hi = 'Bonjour', name = 'toi', result;
result = hi + name;
alert(result); // Affiche : « Bonjourtoi »
Ce code va afficher la phrase « Bonjourtoi ». Vous remarquerez qu’il n’y a pas d’espace
entre les deux mots car la concaténation respecte scrupuleusement ce que vous avez
écrit dans les variables. Pour ajouter un espace, insérez-en un avant ou après l’une des
variables, comme ceci : var hi = 'Bonjour ';.
Par ailleurs, vous souvenez-vous de l’addition suivante ?
var number = 3;
number += 5;
Ainsi, le texte tapé par l’utilisateur se retrouvera directement stocké dans la variable
text.
alert(result);
number = parseInt(text);
alert(typeof number); // Affiche : « number »
alert(number); // Affiche : « 1337 »
Maintenant que vous savez comment vous en servir, on va pouvoir l’adapter à notre
code :
var first, second, result;
alert(result);
Qu’avons-nous fait ? Nous avons juste ajouté une chaîne de caractères vide entre les
deux nombres, ce qui aura eu pour effet de les convertir en une chaîne de caractères.
En résumé
• Une variable permet de stocker une valeur.
• On utilise le mot-clé var pour déclarer une variable, et on utilise le signe = pour
affecter une valeur à la variable.
• Les variables sont typées dynamiquement, ce qui signifie qu’il est inutile de spécifier
le type de contenu que la variable va contenir.
• Grâce à différents opérateurs, on peut effectuer des opérations entre les variables.
• L’opérateur + permet de concaténer des chaînes de caractères, c’est-à-dire de les
mettre bout à bout.
• La fonction prompt() permet d’interagir avec l’utilisateur.
QCM
(http://odyssey.sdlm.be/javascript/04/partie1/chapitre3/qcm.htm)
Questionnaire didactique
(http://odyssey.sdlm.be/javascript/05/partie1/chapitre3/questionnaire.htm)
Dans le chapitre précédent, vous avez appris à créer et modifier des variables. C’est
déjà bien mais malgré tout, on se sent encore un peu limité dans nos codes. Dans ce
chapitre, vous allez donc découvrir les conditions de tout type et surtout vous rendre
compte que les possibilités pour votre code seront déjà bien plus ouvertes car vos
conditions vont influer directement sur la façon dont va réagir votre code à certains
critères.
En plus des conditions, vous allez aussi pouvoir approfondir vos connaissances sur un
fameux type de variable : le booléen.
OPÉRATEUR SIGNIFICATION
== égal à
!= différent de
=== contenu et type égal à
!== contenu ou type différent de
> supérieur à
>= supérieur ou égal à
< inférieur à
<= inférieur ou égal à
Nous n’allons pas illustrer chacun d’entre eux par un exemple, mais nous allons au
moins vous montrer comment les utiliser afin que vous puissiez essayer les autres :
var number1 = 2, number2 = 2, number3 = 4, result;
Comme vous le voyez, le principe n’est pas bien compliqué : il suffit d’écrire deux
valeurs avec l’opérateur de comparaison souhaité entre les deux et un booléen est
retourné. Si celui-ci est true alors la condition est vérifiée, si c’est false elle ne l’est
pas.
Les opérateurs === et !== peuvent être difficiles à comprendre pour un débutant. Afin
que vous ne soyez pas perdus, voyons leur fonctionnement avec quelques exemples :
var number = 4, text = '4', result;
Avez-vous cerné leur principe ? Les conditions « normales » effectuent des conversions
de type pour vérifier les égalités. Ainsi, si vous voulez différencier le chiffre 4 d’une
chaîne de caractères contenant le chiffre 4, vous devrez utiliser l’opérateur ===.
Vous disposez à présent de tous les outils dont vous avez besoin pour réaliser quelques
expérimentations. Passons maintenant à la suite.
L’opérateur ET
Cet opérateur vérifie la condition lorsque toutes les valeurs qui lui sont passées valent
true. Si une seule d’entre elles vaut false, alors la condition ne sera pas vérifiée.
L’opérateur OU
Cet opérateur est plus « souple » car il renvoie true si une des valeurs qui lui est
soumise contient true, qu’elles que soient les autres valeurs.
var result = true || true;
alert(result); // Affiche : « true »
L’opérateur NON
Cet opérateur se différencie des deux autres car il ne prend qu’une seule valeur à la
fois. S’il se nomme « NON » c’est parce que sa fonction est d’inverser la valeur qui lui
est passée. Ainsi, true deviendra false et inversement.
var result = false;
result = !result;
alert(result); // Affiche « false » car on a inversé de nouveau
// « result », on est donc passé de « true » à « false »
Il est bien entendu possible de raccourcir le code en combinant tout cela sur une seule
ligne (comme ce sera le cas pour toutes les conditions dans ce tutoriel) :
var result = 2 > 8 && 8 > 2;
alert(result); // Affiche « false »
Voilà tout pour les booléens et les opérateurs conditionnels, nous allons enfin pouvoir
commencer à utiliser les conditions comme il se doit.
La condition if else
Nous abordons enfin les conditions, ou plus exactement les structures conditionnelles.
Nous parlerons par la suite de « condition » simplement, plus rapide à écrire et à lire.
Avant toute chose, précisons qu’il existe trois types de conditions. Nous allons
commencer par if else qui est la condition la plus utilisée.
if (false) {
alert("Pas la peine d'insister, ce message ne s'affichera pas.");
}
Jusqu’à présent nous décomposions toutes les étapes d’une condition dans plusieurs
variables. Nous vous conseillons désormais de tout mettre sur une seule et même ligne
car ce sera plus rapide à écrire pour vous et plus facile à lire pour tout le monde.
Comme vous pouvez le constater, le code s’exécute lorsque vous cliquez sur le bouton
OK (la fonction renvoie true) et il ne s’exécute pas lorsque vous cliquez sur Annuler
(elle renvoie false). Cette fonction est très pratique à utiliser avec les conditions.
Nous pouvons maintenant revenir à nos conditions.
if (confirm('Pour accéder à ce site, vous devez avoir 18 ans ou plus, cliquez sur
"OK" si c\'est le cas.')) {
alert('Vous allez être redirigé vers le site.');
}
else {
alert("Désolé, vous n'avez pas accès à ce site.");
}
Ainsi, la structure else suit directement l’accolade de fermeture de la structure if, pas
de risque de se tromper quant au fait de savoir quelle structure else appartient à quelle
structure if. Et c’est un peu plus « propre » à lire. Mais rien ne vous oblige à adopter
cette présentation, il s’agit juste d’un conseil.
if (floor == 0) {
} else {
La condition switch
Nous venons d’étudier le fonctionnement de la condition if else qui est très utile dans
de nombreux cas. Toutefois, elle n’est pas très pratique pour faire du cas par cas ; c’est
là qu’intervient switch.
Prenons un exemple : nous avons un meuble avec quatre tiroirs contenant chacun des
objets différents, et il faut que l’utilisateur puisse connaître le contenu du tiroir dont il
saisit le chiffre. Si nous voulions le faire avec if else, ce serait assez long et
fastidieux :
var drawer = parseInt(prompt('Choisissez le tiroir à ouvrir (1 à 4) :'));
if (drawer == 1) {
} else if (drawer == 2) {
} else if (drawer == 3) {
} else if (drawer == 4) {
} else {
C’est long, non ? Et en plus ce n’est pas très adapté à ce qu’on souhaite faire. Le plus
gros problème est de devoir réécrire à chaque fois la condition. Voyons maintenant la
syntaxe avec switch, plus facile :
var drawer = parseInt(prompt('Choisissez le tiroir à ouvrir (1 à 4) :'));
switch (drawer) {
case 1:
alert('Contient divers outils pour dessiner : du papier, des crayons, etc.');
break;
case 2:
alert('Contient du matériel informatique : des câbles, des composants,
etc.');
break;
case 3:
alert('Ah ? Ce tiroir est fermé à clé ! Dommage !');
break;
case 4:
alert('Contient des vêtements : des chemises, des pantalons, etc.');
break;
default:
alert("Info du jour : le meuble ne contient que 4 tiroirs et, jusqu'à preuve
du contraire, les tiroirs négatifs n'existent pas.");
}
Dans certains cas, ce système peut être pratique mais cela reste extrêmement rare.
Avant de clore cette partie, il est nécessaire que vous compreniez un point essentiel : un
switch permet de faire une action en fonction d’une valeur mais aussi en fonction du
type de la valeur (comme l’opérateur ===), ce qui veut dire que ce code n’affichera
jamais « Bravo ! » :
var drawer = prompt('Entrez la valeur 1 :');
switch (drawer) {
case 1:
alert('Bravo !');
break;
default:
alert('Perdu !');
}
En effet, nous avons retiré la fonction parseInt() de notre code, ce qui veut dire que
nous passons une chaîne de caractères à notre switch. Puisque ce dernier vérifie aussi
les types des valeurs, le message « Bravo ! » ne sera jamais affiché.
En revanche, si nous modifions notre premier case pour vérifier une chaîne de
caractères plutôt qu’un nombre alors nous n’avons aucun problème :
var drawer = prompt('Entrez la valeur 1 :');
switch (drawer) {
case '1':
alert('Bravo !');
break;
default:
alert('Perdu !');
}
Les ternaires
Et voici enfin le dernier type de condition, les ternaires. Vous allez voir qu’elles sont
très particulières, tout d’abord parce qu’elles sont très rapides à écrire (mais peu
lisibles) et surtout parce qu’elles renvoient une valeur.
Pour que vous puissiez bien comprendre dans quel cas de figure vous pouvez utiliser les
ternaires, nous allons commencer par un petit exemple avec la condition if else :
var startMessage = 'Votre catégorie : ',
endMessage,
adult = confirm('Êtes-vous majeur ?');
alert(startMessage + endMessage);
alert(startMessage + endMessage);
Pas très compliqué, n’est-ce pas ? Les ternaires sont des conditions très simples et
rapides à écrire, mais elles ont la mauvaise réputation d’être assez peu lisibles (on ne
les remarque pas facilement dans un code de plusieurs lignes). Beaucoup de personnes
en déconseillent l’utilisation. Pour notre part, nous vous conseillons plutôt de vous en
servir car elles sont très utiles. Si vous épurez bien votre code, les ternaires seront
facilement visibles. Voici un exemple de code à éviter :
alert('Votre catégorie : ' + (confirm('Êtes-vous majeur ?') ? '18+' : '-18'));
Notre code initial faisait onze lignes, ici tout est condensé en une seule ligne. Toutefois,
il faut reconnaître que ce code est très peu lisible. Les ternaires sont très utiles pour
raccourcir des codes mais il ne faut pas pousser leurs capacités à leur paroxysme afin
de ne pas vous retrouver avec un code que vous ne saurez plus lire vous-même.
Bref, les ternaires c’est bon, mangez-en ! Mais pas jusqu’à l’indigestion !
if (conditionTest) {
alert('Fonctionne !');
} else {
alert('Ne fonctionne pas !');
}
Le cas de l’opérateur OU
L’opérateur OU est un cas particulier également. En plus de sa fonction principale, il
permet de renvoyer la première variable possédant une valeur évaluée à true.
var conditionTest1 = '', conditionTest2 = 'Une chaîne de caractères';
alert(conditionTest1 || conditionTest2);
Présentation de l’exercice
Nous allons ici essayer d’afficher un commentaire selon l’âge de la personne.
Correction
Voici le code attendu :
var age = parseInt(prompt('Quel est votre âge ?')); // Ne pas oublier : il
// faut "parser" (cela consiste à analyser) la valeur renvoyée par prompt()
// pour avoir un nombre !
alert("Oh vraiment ? Vous avez moins d'un an ? C'est pas très crédible =p");
} else if (age > 120) { // Ne pas oublier les plus de 120 ans, ils n'existent
// probablement pas mais on le met dans le doute
En résumé
• Une condition retourne une valeur booléenne : true ou false.
• De nombreux opérateurs existent afin de tester des conditions et ils peuvent être
combinés entre eux.
• La condition if else est la plus utilisée et permet de combiner les conditions.
• Quand il s’agit de tester une égalité entre une multitude de valeurs, la condition
switch est préférable.
• Les ternaires sont un moyen concis d’écrire des conditions if else et présentent
l’avantage de retourner une valeur.
QCM
(http://odyssey.sdlm.be/javascript/08/partie1/chapitre4/qcm.htm)
Questions ouvertes
(http://odyssey.sdlm.be/javascript/10/partie1/chapitre4/questions.htm)
Les programmeurs sont réputés pour être des gens fainéants, ce qui n’est pas totalement
faux puisque le but de la programmation est de faire exécuter des choses à un ordinateur
pour ne pas les faire soi-même. Dans ce chapitre, nous allons voir comment répéter des
actions pour ne pas avoir à écrire plusieurs fois les mêmes instructions. Mais avant
cela, nous nous intéresserons à l’incrémentation.
L’incrémentation
Considérons le calcul suivant :
var number = 0;
number = number + 1;
Le fonctionnement
L’incrémentation permet d’ajouter une unité à un nombre au moyen d’une syntaxe courte.
À l’inverse, la décrémentation permet de soustraire une unité.
var number = 0;
number++;
alert(number); // Affiche : « 1 »
number--;
alert(number); // Affiche : « 0 »
Il s’agit donc d’une méthode assez rapide pour ajouter ou soustraire une unité à une
variable (on dit « incrémenter » et « décrémenter »), et cela nous sera particulièrement
utile tout au long de ce chapitre.
number_1++;
++number_2;
alert(number_1); // Affiche : « 1 »
alert(number_2); // Affiche : « 1 »
Les variables number_1 et number_2 ont toutes deux été incrémentées. Quelle est donc
la différence entre les deux procédés ?
La différence réside dans la priorité de l’opération, ce qui a de l’importance si vous
voulez récupérer le résultat de l’incrémentation. Dans l’exemple suivant, ++number
retourne la valeur de number incrémentée, c’est-à-dire 1.
var number = 0;
var output = ++number;
alert(number); // Affiche : « 1 »
alert(output); // Affiche : « 1 »
alert(number); // Affiche : « 1 »
alert(output); // Affiche : « 0 »
La boucle while
Une boucle est une structure analogue aux structures conditionnelles vues dans le
chapitre précédent sauf qu’il s’agit ici de répéter une série d’instructions. La répétition
se fait jusqu’à ce qu’on dise à la boucle de s’arrêter. À chaque fois que la boucle se
répète, on parle d’« itération » (qui est en fait un synonyme de répétition).
Fonctionnement de la boucle while
Pour faire fonctionner une boucle, il est nécessaire de définir une condition. Tant que
celle-ci est vraie (true), la boucle se répète. Dès que la condition est fausse (false),
la boucle s’arrête.
Voici un exemple de la syntaxe d’une boucle while :
while (condition) {
instruction_1;
instruction_2;
instruction_3;
}
alert(number); // Affiche : « 10 »
Au départ, number vaut 1. Arrive ensuite la boucle qui va demander si number est
strictement plus petit que 10. Comme c’est vrai, la boucle est exécutée et number est
incrémenté. À chaque fois que les instructions présentes dans la boucle sont exécutées,
la condition de la boucle est réévaluée pour savoir s’il faut réexécuter la boucle ou non.
Dans cet exemple, la boucle se répète jusqu’à ce que number soit égal à 10. Si number
vaut 10, la condition number < 10 est fausse et la boucle s’arrête. Quand la boucle
s’arrête, les instructions qui suivent la boucle (la fonction alert() dans notre exemple)
sont exécutées normalement.
Exemple pratique
Imaginons un petit script qui va demander à l’internaute son prénom, ainsi que les
prénoms de ses frères et sœurs. Cela ne semble pas compliqué à faire puisqu’il s’agit
d’afficher une boîte de dialogue à l’aide de prompt() pour chaque prénom. Seulement,
comment savoir à l’avance le nombre de frères et sœurs ?
Nous allons utiliser une boucle while, qui va demander, à chaque passage dans la
boucle, un prénom supplémentaire. La boucle ne s’arrêtera que lorsque l’utilisateur
choisira de ne plus entrer de prénom.
var nicks = '',
nick,
proceed = true;
while (proceed) {
nick = prompt('Entrez un prénom :');
if (nick) {
nicks += nick + ' '; // Ajoute le nouveau prénom ainsi qu'une
// espace juste après
} else {
proceed = false; // Aucun prénom n'a été entré, donc on fait en
// sorte d'invalider la condition
}
}
Quelques améliorations
Utilisation de break
Dans l’exemple des prénoms, nous utilisons une variable de boucle pour pouvoir arrêter
la boucle. Cependant, il existe un mot-clé pour arrêter la boucle d’un seul coup. Ce mot-
clé est break, il s’utilise exactement comme dans la structure conditionnelle switch
vue au chapitre précédent. Voici donc le même code que précédemment en utilisant
break :
var nicks = '',
nick;
while (true) {
nick = prompt('Entrez un prénom :');
if (nick) {
nicks += nick + ' '; // Ajoute le nouveau prénom ainsi qu'une
// espace juste après
} else {
break; // On quitte la boucle
}
}
Utilisation de continue
Cette instruction est plus rare, car les opportunités de l’utiliser ne sont pas toujours
fréquentes. continue, un peu comme break, permet de mettre fin à une itération mais
elle ne provoque pas la fin de la boucle : l’itération en cours est stoppée et la boucle
passe à l’itération suivante.
La boucle do while
La boucle do while ressemble très fortement à la boucle while sauf que dans ce cas, la
boucle est toujours exécutée au moins une fois. Avec une boucle while, si la condition
n’est pas valide, la boucle n’est pas exécutée. Avec do while, la boucle est exécutée
une première fois, puis la condition est testée pour savoir si la boucle doit continuer.
Voici la syntaxe d’une boucle do while :
do {
instruction_1;
instruction_2;
instruction_3;
} while (condition);
On note donc une différence fondamentale dans l’écriture par rapport à la boucle while,
ce qui permet de bien faire la différence entre les deux. Cela dit, l’utilisation des
boucles do while n’est pas très fréquente, et il est fort possible que vous n’en ayez
jamais l’utilité car généralement les programmeurs utilisent une boucle while normale,
avec une condition qui fait que celle-ci est toujours exécutée une fois.
Dans les parenthèses de la boucle, on ne trouve pas uniquement la condition, mais trois
blocs : initialisation, condition et incrémentation. Ces trois blocs sont séparés
par un point-virgule ; c’est un peu comme si les parenthèses contenaient trois
instructions distinctes.
Dans le premier bloc, initialisation, on initialise une variable appelée iter qui vaut 0 ;
le mot-clé var est requis, comme pour toute initialisation. Dans le bloc condition, on
indique que la boucle continue tant que iter est strictement inférieure à 5. Enfin, dans
le bloc d’incrémentation, on indique que iter sera incrémentée à chaque itération
terminée.
Cependant, le résultat affiche uniquement « Itération n°4 » à la fin et il n’y a pas
d’itération n°5. C’est tout à fait normal, pour deux raisons : le premier tour de boucle
porte l’indice 0, donc si on compte de 0 à 4, il y a bien 5 tours : 0, 1, 2, 3 et 4. Ensuite,
l’incrémentation n’a pas lieu avant chaque itération, mais à la fin de chacune. Donc, le
tout premier tour de boucle est effectué avec iter qui vaut 0, avant d’être incrémentée.
if (nick) {
nicks += nick + ' ';
} else {
break;
}
}
alert(nicks);
if (nick) {
nicks += nick + ' ';
} else {
break;
}
}
Les caractères \n permettent d’ajouter des sauts de ligne (deux dans le code
précédent).
Priorité d’exécution
Les trois blocs qui constituent la boucle for ne sont pas exécutés en même temps :
• initialisation : ce bloc est exécuté juste avant que la boucle ne démarre. C’est comme
si les instructions d’initialisation avaient été écrites juste avant la boucle, un peu
comme pour une boucle while ;
• condition : ce bloc est exécuté avant chaque passage de boucle, exactement comme la
condition d’une boucle while ;
• incrémentation : ce bloc est exécuté après chaque passage de boucle. Cela signifie
que si vous ajoutez un break dans une boucle for, le passage dans la boucle lors du
break ne sera pas comptabilisé.
La boucle for est très utilisée en JavaScript, bien plus que la boucle while. Comme
nous le verrons par la suite, le fonctionnement même du JavaScript fait que la boucle
for est nécessaire dans la majorité des cas comme la manipulation des tableaux ainsi
que des objets. Nous étudierons aussi une variante de la boucle for, appelée for in,
que nous ne pouvons pas aborder maintenant car elle ne s’utilise que dans certains cas
spécifiques.
En résumé
• L’incrémentation est importante au sein des boucles. Incrémenter ou décrémenter
signifie ajouter ou soustraire une unité à une variable. Le comportement d’un
opérateur d’incrémentation est différent s’il se place avant ou après la variable.
• La boucle while permet de répéter une liste d’instructions tant que la condition est
vérifiée.
• La boucle do while est une variante de while qui sera exécutée au moins une fois,
peu importe la condition.
• La boucle for est une boucle utilisée pour répéter une liste d’instructions un certain
nombre de fois. C’est donc une variante très ciblée de la boucle while.
QCM
(http://odyssey.sdlm.be/javascript/13/partie1/chapitre5/qcm.htm)
Questionnaire didactique
(http://odyssey.sdlm.be/javascript/14/partie1/chapitre5/questionnaire.htm)
Voici un chapitre très important, tant par sa longueur que par les connaissances qu’il
permet d’acquérir ! Vous allez y découvrir ce que sont exactement les fonctions et
comment en créer vous-mêmes. Vous verrez comment gérer vos variables dans les
fonctions, utiliser des arguments, retourner des valeurs, créer des fonctions dites
« anonymes », etc., soit tout ce dont vous avez besoin pour créer des fonctions utiles !
Bien entendu, tout comme les variables, les noms de fonctions sont limités aux
caractères alphanumériques (dont les chiffres) et aux deux caractères suivants :
_ et $.
Maintenant que vous connaissez un peu le principe d’une fonction, voici un petit
exemple :
function showMsg() {
alert('Et une première fonction, une !');
}
Un exemple concret
Comme mentionné précédemment, l’intérêt d’une fonction réside notamment dans le fait
de ne pas avoir à réécrire plusieurs fois le même code. Nous allons ici étudier un cas
intéressant où l’utilisation d’une fonction va se révéler utile :
var result;
Comme vous pouvez le constater, nous avons écrit deux fois le même code, ce qui nous
donne un résultat peu efficace. Nous pouvons envisager d’utiliser une boucle mais si
nous voulons afficher un texte entre les deux opérations, la boucle devient inutilisable :
var result;
La solution ici consiste donc à faire appel au système des fonctions de cette façon :
function byTwo() {
var result = parseInt(prompt('Donnez le nombre à multiplier par 2 :'));
alert(result * 2);
}
byTwo();
byTwo();
function sayHello() {
alert(ohai);
}
sayHello();
sayHello();
alert(ohai);
Lorsqu’une variable n’est accessible que dans une partie de votre code, on dit
qu’elle se trouve au sein d’un « scope ». Retenez bien ce terme, il vous servira
à l’avenir.
function showMsg() {
var message = 'Ici la variable locale !';
alert(message);
}
showMsg();
alert(message);
function calculate() {
alert(var1 * var2); // Affiche : « 6 » (sans blague ?!)
}
calculate();
Dans ce code, vous pouvez voir que les variables var1 et var2 ne sont utilisées que
pour la fonction calculate(), or il s’agit ici de variables globales. Sur le principe,
cette façon de faire est absurde : étant donné que ces variables ne servent qu’à la
fonction calculate(), autant les déclarer dans la fonction de la manière suivante :
function calculate() {
var var1 = 2,
var2 = 3;
alert(var1 * var2);
}
calculate();
Ainsi, ces variables n’iront pas interférer avec d’autres fonctions qui peuvent utiliser
des variables de même nom. Et surtout, cela reste quand même plus logique !
Les variables globales sont parfois décriées à tort. Lorsqu’elles sont utilisées à
bon escient, elles peuvent être très pratiques.
La partie concernant la portée des variables est terminée. Cela peut vous paraître
simple au premier abord, mais il est facile de se faire piéger. Effectuez tous les tests qui
vous passent par la tête afin de bien explorer toutes les possibilités et les éventuels
pièges.
Les arguments
Créer et utiliser un argument
Comme nous venons de le voir, les arguments sont des informations envoyées à une
fonction. Ces informations peuvent servir à beaucoup de choses, libre à vous de les
utiliser comme vous le souhaitez. D’ailleurs, vous avez déjà envoyé des arguments à
certaines fonctions, par exemple avec la fonction alert() :
// Voici la fonction alert sans argument, elle n'affiche rien :
alert();
Selon les fonctions, il sera parfois inutile de spécifier des arguments ou alors vous
devrez en indiquer un, voire plusieurs. Il existe aussi des arguments facultatifs que vous
n’êtes pas obligés de spécifier.
Pour créer une fonction avec un argument, il vous suffit d’écrire votre code de la façon
suivante :
function myFunction(arg) { // Vous pouvez mettre une espace entre le nom
// de la fonction et la parenthèse ouvrante si
// vous le souhaitez, la syntaxe est libre !
// Votre code…
}
Ainsi, si vous passez un argument à cette même fonction, vous retrouverez dans la
variable arg ce qui a été passé en paramètre. Exemple :
function myFunction(arg) { // Notre argument est la variable « arg »
// Une fois que l'argument a été passé à la fonction, vous allez le
// retrouver dans la variable « arg »
alert('Votre argument : ' + arg);
}
Lorsque nous indiquons que nous passons des valeurs en paramètres d’une
fonction, cela signifie que ces valeurs deviennent les arguments de cette
fonction, tout simplement. Ces deux manières de désigner les choses sont
couramment utilisées, mieux vaut donc savoir ce qu’elles signifient.
Ce fonctionnement est exactement le même que lorsque vous créez une variable dans la
fonction : elle ne sera accessible que dans cette fonction et nulle part ailleurs. Les
arguments sont propres à leur fonction, ils ne serviront à aucune autre fonction.
Comme vous pouvez le constater, les différents arguments sont séparés par une virgule,
comme lorsque vous voulez déclarer plusieurs variables avec un seul mot-clé var. Pour
exécuter notre fonction, il ne nous reste plus qu’à passer les arguments souhaités à notre
fonction, de cette manière :
moar('Un !', 'Deux !');
Bien sûr, nous pouvons toujours faire interagir l’utilisateur sans problème :
moar(prompt('Entrez votre premier argument :'), prompt('Entrez votre deuxième
argument :'));
optional();
De cette façon, si l’argument n’a pas été spécifié pour la variable allowCancel
(comme dans cet exemple), on attribue la valeur false à cette dernière. Bien sûr, les
arguments facultatifs ne possèdent pas obligatoirement une valeur par défaut, mais au
moins vous saurez comment faire si vous en avez besoin.
Petit piège à éviter : inversons le positionnement des arguments de notre fonction (le
second passe en premier). L’argument facultatif est donc en premier et l’argument
obligatoire en seconde position. La première ligne de notre code est donc modifiée de
cette façon :
function prompt2(allowCancel, text) {
Maintenant, essayons d’ajouter une ligne de code après la ligne contenant notre
return :
function sayHello() {
return 'Bonjour !';
alert('Attention ! Le texte arrive !');
}
alert(sayHello());
Mais si cette fonction n’a pas de nom, comment faire pour l’exécuter ? Il existe de très
nombreuses façons, mais pour le moment nous allons nous limiter à une seule solution :
assigner notre fonction à une variable. Nous verrons les autres solutions dans les
chapitres suivants.
Pour assigner une fonction anonyme à une variable, rien de plus simple :
var sayHello = function() {
alert('Bonjour !');
};
Ainsi, il ne nous reste plus qu’à appeler notre fonction par le biais du nom de la
variable à laquelle nous l’avons affectée :
sayHello(); // Affiche : « Bonjour ! »
Il s’agit d’une structure seule, pas besoin de point-virgule. Alors que si j’écris :
var instruction = 1234;
Il s’agit d’une instruction permettant d’assigner une valeur à une variable, le point-
virgule est donc nécessaire.
Maintenant, si j’écris de cette manière :
var instruction = function() {
// Du code…
};
Il s’agit d’une instruction assignant une structure à une variable, le point-virgule est
donc toujours nécessaire car, malgré la présence d’une structure, l’action globale reste
bien une instruction.
(function() {
// Code isolé
})();
// Code externe
La syntaxe peut vous sembler étrange, nous allons donc la détailler pas à pas.
Tout d’abord, nous distinguons une fonction anonyme :
function() {
// Code isolé
}
La présence de ces parenthèses s’explique par le fait qu’une fonction, lorsqu’elle est
déclarée, n’exécute pas immédiatement le code qu’elle contient mais attend d’être
appelée. Or, nous souhaitons exécuter ce code immédiatement. La solution consiste donc
à utiliser ce couple de parenthèses.
Pour expliquer simplement, prenons l’exemple d’une fonction nommée :
function test() {
// Du code…
}
test();
Le premier couple de parenthèses permet de dire « je désigne cette fonction » pour que
l’on puisse ensuite indiquer, avec le second couple de parenthèses, que l’on souhaite
l’exécuter. Le code évolue donc de cette manière :
// Ce code :
(function test() {
})();
// Devient :
(test)();
// Qui devient :
test();
Pour une fonction nommée, la solution sans ces deux couples de parenthèses est plus
propre. Pour une fonction anonyme, nous n’avons pas le choix : nous ne pouvons plus
appeler une fonction anonyme une fois qu’elle est déclarée (sauf si elle a été assignée à
une variable), c’est pourquoi nous devons utiliser les parenthèses.
Une fois les parenthèses ajoutées, la fonction (qui est une structure) est exécutée. Nous
obtenons donc une instruction à laquelle il convient d’ajouter un point-virgule :
(function() {
// Code isolé
})();
})(); // Fin de la zone isolée. Les variables créées dans cette zone sont
// détruites.
})();
Ce chapitre sur les fonctions est terminé. Il est très important aussi je vous conseille de
le lire plusieurs fois si vous n’avez pas encore tout compris et surtout exercez-vous.
En résumé
• Il existe des fonctions natives, mais il est aussi possible d’en créer, avec le mot-clé
function.
• Les variables déclarées avec var au sein d’une fonction ne sont accessibles que dans
cette fonction.
• Il faut éviter le plus possible d’avoir recours aux variables globales.
• Une fonction peut recevoir un nombre défini ou indéfini de paramètres. Elle peut
aussi retourner une valeur ou ne rien retourner.
• Les fonctions qui ne portent pas de nom sont des fonctions anonymes et servent à
isoler une partie du code.
QCM
(http://odyssey.sdlm.be/javascript/17/partie1/chapitre6/qcm.htm)
Questionnaire didactique
(http://odyssey.sdlm.be/javascript/18/partie1/chapitre6/questionnaire.htm)
Les objets sont une notion fondamentale en JavaScript. Dans ce chapitre, nous verrons
comment les utiliser, ce qui nous permettra d’aborder les tableaux, un type d’objet bien
particulier et très courant en JavaScript. Nous découvrirons comment créer des objets
simples et des objets littéraux, qui vont se révéler rapidement indispensables.
Le JavaScript n’est pas un langage orienté objet du même style que le C++, le
C# ou le Java. Le JavaScript est un langage orienté objet par prototype. Si
vous avez déjà des notions de programmation orientée objet, vous constaterez
quelques différences au sein de ce chapitre. Mais les principaux changements
viendront par la suite, lors de la création d’objets.
Le constructeur
Le constructeur est un code qui est exécuté quand on utilise un nouvel objet. Il permet
d’effectuer des actions comme définir diverses variables au sein même de l’objet (par
exemple le nombre de caractères d’une chaîne de caractères). Tout cela est fait
automatiquement pour les objets natifs, nous en reparlerons quand nous aborderons
l’orienté objet.
Les propriétés
Toute valeur va être placée dans une variable au sein de l’objet : c’est ce que l’on
appelle une « propriété ». Une propriété est une variable contenue dans l’objet, elle
contient des informations nécessaires au fonctionnement de ce dernier.
Les méthodes
Il est possible de modifier l’objet grâce aux méthodes, qui sont des fonctions contenues
dans l’objet et qui permettent de réaliser des opérations sur son contenu. Par exemple,
dans le cas d’une chaîne de caractères, il existe une méthode qui permet de la passer en
majuscules.
Exemple d’utilisation
Nous allons créer une chaîne de caractères et afficher ensuite le nombre de caractères
qu’elle contient puis la transformer en majuscules.
var myString = 'Ceci est une chaîne de caractères';
// On crée un objet String
alert(myString.length);
// On affiche le nombre de caractères, au moyen de la propriété « length »
alert(myString.toUpperCase());
// On récupère la chaîne en majuscules, avec la méthode toUpperCase()
Les tableaux
Dans l’un des exemples du chapitre sur les boucles, il était question de demander à
l’utilisateur les prénoms de ses frères et sœurs. Les prénoms étaient concaténés dans
une chaîne de caractères, puis affichés. Cette méthode de stockage ne permettait pas
grand-chose, sauf réafficher les prénoms tels quels.
C’est dans un tel cas que les tableaux entrent en jeu. Il s’agit d’une variable qui contient
plusieurs valeurs, appelées « items ». Chaque item est accessible au moyen d’un indice
(index en anglais), dont la numérotation commence à partir de 0. Voici un schéma
représentant un tableau qui stocke cinq items :
Les indices
Comme indiqué dans le tableau précédent, la numérotation des items commence à 0.
Ceci est très important car il y aura toujours un décalage d’une unité : l’item 1 porte
l’indice 0, l’item 5 porte l’indice 4… Vous devrez donc faire très attention à ne pas
vous emmêler les pinceaux, sans quoi cela sera problématique.
Déclarer un tableau
On utilise bien évidemment var pour déclarer un tableau, mais la syntaxe pour définir
les valeurs est spécifique :
var myArray = ['Sébastien', 'Laurence', 'Ludovic', 'Pauline', 'Guillaume'];
Le contenu du tableau se définit entre crochets et chaque valeur est séparée par une
virgule. Les valeurs sont introduites comme pour des variables simples, c’est-à-dire
qu’il faut des guillemets ou des apostrophes pour définir les chaînes de caractères :
var myArray_a = [42, 12, 6, 3];
var myArray_b = [42, 'Sébastien', 12, 'Laurence'];
myArray[1] = 'Clarisse';
Comme dit précédemment, les méthodes sont des fonctions et elles peuvent donc
recevoir des paramètres. Ici, push() peut recevoir un nombre illimité de paramètres,
chacun d’entre eux représentant un item à ajouter à la fin du tableau.
La méthode unshift() fonctionne comme push(), excepté que les items sont ajoutés au
début du tableau. Cette méthode n’est pas très fréquente mais peut être utile.
Les méthodes shift() et pop() retirent respectivement le premier et le dernier élément
du tableau.
var myArray = ['Sébastien', 'Laurence', 'Ludovic', 'Pauline', 'Guillaume'];
alert(cousinsString);
alert(cousinsArray);
Remarquez que lorsque vous affichez un tableau via alert(), les éléments
sont séparés par des virgules et il n’y a pas d’apostrophes ou de guillemets.
Ceci vient de l’utilisation de alert() qui, pour afficher un objet (un tableau,
un booléen, un nombre…), le transforme en une chaîne de caractères grâce à
une méthode nommée toString().
alert(cousinsString_2);
Comme vous pouvez le constater, une méthode peut très bien retourner une
valeur, tout comme le ferait une fonction indépendante d’un objet. D’ailleurs,
on constate que split() et join() retournent toutes les deux le résultat de
leur exécution, elles ne l’appliquent pas directement à l’objet.
Parcourir un tableau
Soyez attentifs, cette partie est très importante ! Parcourir un tableau est quelque chose
que vous allez faire très fréquemment en JavaScript, surtout plus tard, quand nous
verrons comment interagir avec les éléments HTML.
Dans le chapitre sur les boucles, nous avons étudié la boucle for, que nous allons
utiliser pour parcourir les tableaux. La boucle while peut aussi être utilisée, mais for
est plus adaptée. Nous aborderons aussi une variante de for : la boucle for in.
Parcourir avec for
Reprenons le tableau avec les prénoms :
var myArray = ['Sébastien', 'Laurence', 'Ludovic', 'Pauline', 'Guillaume'];
Le principe pour parcourir un tableau est simple : il faut faire autant d’itérations qu’il y
a d’items. Le nombre d’items d’un tableau se récupère avec la propriété length,
exactement comme pour le nombre de caractères d’une chaîne de caractères. À chaque
itération, nous avançons d’un item dans le tableau, en utilisant la variable de boucle i :
à chaque itération, elle s’incrémente, ce qui permet d’avancer dans le tableau item par
item. Voici un exemple :
for (var i = 0; i < myArray.length; i++) {
alert(myArray[i]);
Le nombre d’items est différent des indices. S’il y a cinq items, comme ici, les
indices vont de 0 à 4. Autrement dit, le dernier item possède l’indice 4, et non
5. C’est très important pour la condition, car on pourrait croire à tort que le
nombre d’items est égal à l’indice du dernier item.
Attention à la condition
Nous avons volontairement mal rédigé le code précédent. En effet, dans le chapitre sur
les boucles, nous avons dit que le deuxième bloc d’une boucle for, le bloc de
condition, était exécuté à chaque itération. Ici, ça veut donc dire que myArray.length
est utilisé à chaque itération, ce qui, à part ralentir la boucle, n’a que peu d’intérêt
puisque le nombre d’items du tableau ne change normalement pas (dans le cas contraire,
n’utilisez pas la solution qui suit).
L’astuce est de définir une seconde variable, dans le bloc d’initialisation, qui contiendra
la valeur de length. On utilisera cette variable pour la condition :
for (var i = 0, c = myArray.length; i < c; i++) {
alert(myArray[i]);
Nous utilisons c comme nom de variable, qui signifie count (compter), mais vous
pouvez utiliser ce que vous voulez.
Pour les objets, c’est à peu près pareil sauf qu’on met des accolades à la place des
crochets :
var myObject = {};
Pour définir dès l’initialisation les items à ajouter à l’objet, il suffit d’écrire :
var myObject = {
item1: 'Texte 1',
item2: 'Texte 2'
};
Comme l’indique ce code, il suffit de saisir l’identifiant souhaité suivi d’un deux-points
et de la valeur à lui attribuer. La séparation des items se fait comme pour un tableau,
avec une virgule.
Il existe une autre manière, semblable à celle qui permet d’accéder aux items d’un
tableau en connaissant l’indice, sauf qu’ici on va simplement spécifier le nom de la
propriété :
family['sister'];
Cela va nous être particulièrement utile si l’identifiant est contenu dans une variable,
comme ce sera le cas avec la boucle que nous allons voir après. Exemple :
var id = 'sister';
Cette façon de faire convient également aux propriétés de tout objet. Ainsi, si
notre tableau se nomme myArray, nous pouvons faire myArray['length']
pour récupérer le nombre d’items.
ou alors :
family.uncle = 'Didier'; // Même opération mais d'une autre manière
alert(family[id]);
return {
x: 12,
y: 21
};
}
alert(coords.x); // 12
alert(coords.y); // 21
La valeur de retour de la fonction getCoords() est mise dans la variable coords, et
l’accès à x et y en est simplifié.
Exercice récapitulatif
Le chapitre 9 contient un TP, c’est-à-dire un travail pratique. Cela dit, avant de le
commencer, nous vous proposons un petit exercice qui reprend de manière simple ce
que nous avons vu dans ce chapitre.
Énoncé
Dans le chapitre sur les boucles, nous avions utilisé un script pour demander à
l’utilisateur les prénoms de ses frères et sœurs. Les prénoms étaient alors stockés dans
une chaîne de caractères. Pour rappel, voici ce code :
var nicks = '',
nick;
while (true) {
nick = prompt('Entrez un prénom :');
if (nick) {
nicks += nick + ' '; // Ajoute le nouveau prénom ainsi qu'une
// espace juste après
} else {
break; // On quitte la boucle
}
}
Ce que nous vous demandons ici, c’est de stocker les prénoms dans un tableau. Pensez à
la méthode push(). À la fin, il faudra afficher le contenu du tableau, avec alert(),
seulement si le tableau contient des prénoms. En effet, il est inutile de l’afficher s’il ne
contient rien. Pour l’affichage, séparez chaque prénom par une espace. Si le tableau ne
contient rien, faites-le savoir à l’utilisateur, toujours avec alert().
Correction
var nicks = [], // Création du tableau vide
nick;
En résumé
• Un objet contient un constructeur, des propriétés et des méthodes.
• Les tableaux sont des variables qui contiennent plusieurs valeurs, chacune étant
accessible au moyen d’un indice.
• Les indices d’un tableau sont toujours numérotés à partir de 0. Ainsi, la première
valeur porte l’indice 0.
• Des opérations peuvent être réalisées sur les tableaux, comme ajouter des items ou
en supprimer.
• Pour parcourir un tableau, on utilise généralement une boucle for, puisqu’on connaît,
grâce à la propriété length, le nombre d’items du tableau.
• Les objets littéraux sont une variante des tableaux où chaque item est accessible via
un identifiant et non un indice.
QCM
(http://odyssey.sdlm.be/javascript/22/partie1/chapitre7/qcm.htm)
Questionnaire didactique
(http://odyssey.sdlm.be/javascript/23/partie1/chapitre7/questionnaire.htm)
Créer des scripts paraît facile au premier abord, mais on finit toujours par tomber sur le
même problème : le code ne fonctionne pas ! On peut alors dire qu’il y a un bogue,
c’est-à-dire une erreur dans le code qui fait qu’il s’exécute mal ou ne s’exécute tout
simplement pas.
Dans ce chapitre, nous allons étudier les différents bogues généralement rencontrés en
JavaScript et surtout, nous allons voir comment les résoudre. Pour cela, nous allons
utiliser les kits de développement fournis avec n’importe quel navigateur digne de ce
nom. Plus particulièrement, nous allons nous pencher sur ceux de Chrome et de Firefox
qui sont sensiblement similaires.
Il faut bien se mettre en tête que l’interpréteur JavaScript ne s’intéresse pas aux valeurs
retournées par votre code, il veut uniquement exécuter le code. Voici la différence entre
le caractère syntaxique et algorithmique d’une erreur : la première empêche le code de
s’exécuter tandis que la seconde ne pose aucun problème d’exécution. Pourtant, les deux
perturbent le bon déroulement du script.
Le débogage
Comme son nom l’indique, cette technique consiste à supprimer les bogues présents
dans un code. Pour chaque type de bogue, vous avez plusieurs solutions spécifiques.
Les bogues syntaxiques sont les plus simples à résoudre, car l’interpréteur JavaScript
vous signalera généralement l’endroit où l’erreur est apparue (cette signalisation peut
être consultée dans la console de votre navigateur, comme nous le verrons plus loin).
En ce qui concerne les bogues algorithmiques, vous allez devoir faire travailler votre
cerveau et chercher tout seul où vous avez bien pu vous tromper. La méthode la plus
simple consiste à remonter les couches de votre code pour trouver à quel endroit s’est
produite l’erreur.
Par exemple, si vous avez un calcul qui affiche une mauvaise valeur, vous allez
immédiatement vérifier ce calcul. Si ce calcul n’est pas en cause mais qu’il fait appel à
des variables, alors vous allez vérifier la valeur de chacune de ces variables, etc. De fil
en aiguille, vous parviendrez à déboguer votre code.
Pour vérifier les valeurs de vos variables, calculs, etc., vous serez peut-être
tentés d’utiliser la fonction alert(). Sachez qu’il existe une méthode
spécialement adaptée à ce cas de figure, il s’agit de console.log().
Vous pouvez voir une ligne écrite en rouge mentionnant SyntaxError, qui indique une
erreur syntaxique. Le texte qui suit n’est qu’une indication sur ce qui a bien pu
provoquer cette erreur, ce n’est généralement pas utile de le lire d’autant plus que
chaque navigateur peut fournir une indication relativement différente. Par exemple,
Firefox indique SyntaxError: missing ; before statement.
À droite de ce texte, vous pouvez voir le nom du fichier concerné ainsi que la ligne de
code. Cliquez dessus, la ligne qui pose problème s’affiche dans votre navigateur :
Par ailleurs, notre code contient deux erreurs. Pourquoi n’y en a-t-il qu’une seule qui est
affichée ? Tout simplement parce que l’interpréteur JavaScript s’arrête sur la première
erreur rencontrée. Essayez de corriger l’erreur actuellement indiquée, le navigateur
affichera alors l’erreur suivante sur le navigateur : Uncaught SyntaxError:
Unexpected end of input. Firefox est un peu plus clair sur ce point, il indique
SyntaxError: missing } after function body.
Notons que la console permet aussi de repérer d’autres erreurs qui ne sont pas
forcément liées au JavaScript (images manquantes, par exemple). Dans l’ensemble, si le
comportement de notre page web ne correspond pas à ce que nous attendions, nous
pouvons toujours consulter la console avant d’appeler à l’aide. L’origine du problème y
est généralement indiquée.
// Et on l'affiche.
console.log(helloObject);
console.log(helloArray);
Le débogueur de Chrome
Le débogueur de Firefox
Un point d’arrêt permet d’indiquer à votre navigateur que vous souhaitez mettre en
pause votre code avant l’exécution de la ligne concernée. Notons que les points d’arrêt
ne peuvent être placés que sur des lignes comportant des instructions. Il est par exemple
impossible d’en placer un sur la déclaration d’une fonction.
Afin que votre point d’arrêt soit pris en compte, vous devez recharger la page. Votre
code sera alors mis en pause à la ligne concernée et celle-ci sera surlignée en bleu.
Vous pourrez consulter les variables actuellement utilisées au sein du scope actuel (ici,
le scope de la fonction a()) ainsi que les variables globales.
Dans Chrome, la consultation des variables s’effectue dans la colonne de droite, sous le
ruban Scope Variables. Les variables du scope courant se trouvent dans le menu Local
et les variables globales dans le menu Global.
La consultation des variables avec Chrome
Sous Firefox, vous trouverez les variables dans la colonne de droite de l’onglet
Variables. Les variables du scope courant se trouvent sous le ruban Portée […)
(« […) » désignant généralement le nom de votre fonction), tandis que les variables
globales sont sous le ruban Portée globale.
Vous pouvez ainsi voir qu’ici, la valeur que notre fonction a() s’apprête à afficher est
2, ce qui est cohérent puisque notre boucle démarre à 0, tandis que les fonctions b() et
c() incrémente toutes les deux la valeur.
Cependant, cela ne concerne que la première exécution de notre fonction a(), qu’en est-
il des autres exécutions ? Il suffit pour ça d’indiquer au débogueur que l’exécution du
script doit reprendre jusqu’à ce qu’on retombe sur un nouveau point d’arrêt. Pour cela,
vous devez cliquer sur le bouton approprié.
Le bouton de Chrome se trouve dans la colonne de droite et est représenté par le
symbole .
Le bouton de Firefox se trouve en haut à gauche et est représenté par le symbole .
Une fois la reprise effectuée, votre code sera à nouveau mis en pause au même point
d’arrêt car la ligne en question est de nouveau exécutée. La variable value aura alors
changée, passant de 2 à 3 car la boucle en est à sa deuxième itération. Si vous reprenez
l’exécution du code, vous verrez alors la valeur augmenter de nouveau et ainsi de suite
jusqu’à la fin de l’exécution du script.
La pile d’exécution
Pour chaque point d’arrêt que vous placez dans votre code, vous pouvez consulter la
pile d’exécution. Cette dernière définit par quelles fonctions votre code est passé afin
d’atteindre la ligne actuellement mise en pause par votre point d’arrêt. Reprenez la page
qui nous a servi pour les points d’arrêt, repositionnez un point d’arrêt sur la ligne 3 et
rafraîchissez la page. Une fois l’exécution du script mise en pause, vous pouvez
observer la pile d’exécution de votre code.
• Sous Chrome, vous pouvez la consulter dans la colonne de droite sous le ruban Call
Stack.
La fonction qui a été exécutée le plus récemment est la première. La dernière représente
l’espace global de votre script, avant qu’une première fonction n’ait été exécutée. On
voit donc très clairement que notre boucle (qui est exécutée dans l’espace global) a fait
appel à la fonction c(), qui a fait appel à b(), qui a elle-même appelé a().
Un point intéressant concernant cette pile est que vous pouvez cliquer sur chacune de
ses étapes et consulter les variables du scope. Si vous cliquez sur l’étape « c » vous
pourrez alors consulter la valeur de la variable value et bien vérifier qu’elle est à 0 à
la première pause du script.
Un autre avantage de cette pile est que parfois, vous ne savez pas par quoi a été
déclenchée une partie de votre code. Ceci résout donc ce problème. Cependant, la pile
d’exécution n’est pas toujours aussi simple à lire pour deux raisons : sa longueur peut
être relativement importante et les fonctions anonymes ne sont pas faciles à identifier, du
fait qu’elles sont anonymes !
Une chose importante à avoir en tête lorsque vous créez une fonction anonyme : il est
possible de lui donner un nom dans la pile d’exécution afin de mieux vous repérer.
Essayez le code suivant en ajoutant un point d’arrêt sur la ligne 3 :
// On utilise ci-dessous une IIFE, on déclare donc une fonction anonyme
// qu'on exécute immédiatement.
(function anonymousWasHere() {
console.log('Hi!');
})();
Voilà qui clôt ce premier chapitre sur le débogage. De nombreuses autres fonctionnalités
sont disponibles dans le panel de débogage de Chrome ou de Firefox et il n’est pas
facile de toutes les évoquer en raison de la densité de ce livre. Pour plus
d’informations, consultez la page de Google pour Chrome
(https://developers.google.com/chrome-developer-tools/docs/javascript-debugging?
hl=FR) ainsi que celle de Mozilla pour Firefox
(https://developer.mozilla.org/fr/docs/Outils/Debugger).
En résumé
• Il existe deux types de bogues : les bogues syntaxiques et les bogues algorithmiques,
lesquels sont souvent plus compliqués à résoudre.
• Tout navigateur récent et un tant soit peu populaire possède un kit de développement
permettant de faciliter le débogage de votre code.
• La console permet d’afficher les erreurs syntaxiques de votre code mais également
d’y afficher du texte ou le contenu d’une variable à l’aide de la méthode
console.log().
• Il est possible d’exécuter du code dans la console et d’accéder aux variables
globales de la page actuelle.
• Les points d’arrêt permettent de mettre en pause votre script à un point précis du
code et d’analyser l’état du scope courant ainsi que de l’espace global.
• La pile d’exécution définit par quelles fonctions votre code est passé afin d’atteindre
la ligne actuellement mise en pause par un point d’arrêt.
9 TP : convertir un nombre en
toutes lettres
Nous arrivons enfin au premier TP de cet ouvrage ! Celui-ci a pour but de vous faire
réviser et de mettre en pratique l’essentiel de ce que vous avez appris dans les chapitres
précédents. Nous vous conseillons donc fortement de le lire et d’essayer de faire
l’exercice proposé. Vous pourrez ainsi réviser l’utilisation des variables, conditions,
boucles, fonctions et tableaux. Ce TP vous permettra aussi d’approfondir vos
connaissances sur l’identification et la conversion des nombres.
Présentation de l’exercice
Ce TP sera consacré à un exercice bien particulier : la conversion d’un nombre en
toutes lettres. Ainsi, si l’utilisateur saisit le nombre « 41 », le script devra retourner ce
nombre en toutes lettres : « quarante et un ». Ne vous inquiétez pas, vous en êtes
parfaitement capables ! Et nous allons même vous aider un peu avant de vous donner le
corrigé.
La marche à suivre
Pour mener à bien votre exercice, voici quelles sont les étapes que votre script devra
suivre (vous n’êtes pas obligés de faire comme ça, mais c’est conseillé).
1. L’utilisateur est invité à entrer un nombre compris entre 0 et 999.
2. Ce nombre doit être envoyé à une fonction qui se charge de le convertir en toutes
lettres.
3. Cette même fonction doit contenir un système permettant de séparer les centaines, les
dizaines et les unités. Ainsi, la fonction doit être capable de voir que le nombre 365
contient trois centaines, six dizaines et cinq unités. Pour obtenir ce résultat, pensez à
utiliser le modulo. Exemple : 365 % 10 = 5.
4. Une fois le nombre découpé en trois chiffres, il ne reste plus qu’à convertir ces
derniers en toutes lettres.
5. Lorsque la fonction a fini de s’exécuter, elle renvoie le nombre en toutes lettres.
6. Une fois le résultat de la fonction obtenu, il est affiché à l’utilisateur.
7. Lorsque l’affichage du nombre en toutes lettres est terminé, on redemande un nouveau
nombre à l’utilisateur.
Nous allons employer les règles orthographiques en vigueur depuis 1990, nous
écrirons donc les nombres de la manière suivante : cinq-cent-cinquante-cinq.
Et non pas: cinq cent cinquante-cinq.
Vu que nous avons déjà reçu pas mal de reproches sur cette manière d’écrire,
nous préférons vous prévenir afin de vous éviter des commentaires inutiles.
Sur certains navigateurs, le chiffre affiché par ce code n’est pas 10 comme souhaité
mais 8 ! La raison à cela est que la fonction parseInt() supporte plusieurs bases
arithmétiques (http://fr.wikipedia.org/wiki/Base_%28arithm%C3%A9tique%29). Nous
pouvons ainsi lui dire que le premier argument est en binaire. La fonction retournera
alors le nombre en base 10 (notre propre base de calcul, le système décimal) après
avoir fait la conversion base 2 (système binaire) → base 10 (système décimal). Donc,
si nous écrivons :
alert(parseInt('100', 2)); // Affiche « 4 »
La fonction isNaN()
Jusqu’à présent, pour tester si une variable était un nombre, vous utilisiez l’instruction
typeof, qui n’est pas sans poser problème :
En effet, dans cet exemple, notre variable contient la valeur NaN (Not a Number) et
pourtant l’instruction typeof renvoie number en guise de type. C’est assez
contradictoire, non ? En fait, l’instruction typeof est limitée pour tester les nombres. Il
est préférable d’utiliser à la place la fonction isNaN() (is Not a Number) :
var test = parseInt('test'); // Contient au final la valeur « NaN »
Pourquoi obtenons-nous true ? Tout simplement parce que isNaN() renvoie true
quand la variable n’est pas un nombre, et false dans le cas contraire.
Correction
Nous espérons que vous avez réussi à obtenir quelque chose d’intéressant, le sujet est
certes un peu tordu, mais vos connaissances sont largement suffisantes pour pouvoir le
réaliser.
Toutefois, si vous rencontrez un blocage alors que vous avez bien compris ce qui a été
dit dans les chapitres précédents, ne vous inquiétez pas. La programmation est un
domaine où la logique règne en maîtresse (bon, d’accord, pas tout le temps !), il faut
donc de l’expérience pour en arriver à développer de façon logique. Si vous n’avez pas
réussi à coder l’exercice aujourd’hui, ce n’est pas un drame ! Faites une pause, essayez
de faire des exercices plus simples et revenez ensuite sur celui-ci. Si vous arrivez à lire
et comprendre le corrigé suivant, alors vous êtes capables de réaliser cet exercice tout
aussi bien que nous, voire mieux !
Le corrigé complet
Précisons tout d’abord que ce code n’est pas universel. Il existe de nombreuses autres
façons de coder cet exercice et cette version n’est pas forcément la meilleure. Si vous
cherchez à recoder cet exercice après avoir lu le corrigé, ne refaites pas exactement la
même chose ! Inventez votre propre solution, innovez selon vos propres idées ! Après
tout, on dit qu’il existe autant d’algorithmes que de personnes dans le monde, car chacun
possède sa propre façon de penser, alors vous devriez pouvoir réaliser une autre
version de ce code en réfléchissant par vous-mêmes !
function num2Letters(number) {
var units2Letters = ['', 'un', 'deux', 'trois', 'quatre', 'cinq', 'six', 'sept',
'huit', 'neuf', 'dix', 'onze', 'douze', 'treize', 'quatorze', 'quinze', 'seize',
'dix-sept', 'dix-huit', 'dix-neuf'],
tens2Letters = ['', 'dix', 'vingt', 'trente', 'quarante', 'cinquante',
'soixante', 'soixante', 'quatre-vingt', 'quatre-vingt'];
if (number === 0) {
return 'zéro';
} else {
unitsOut = (units === 1 && tens > 0 && tens !== 8 ? 'et-' : '') +
units2Letters[units];
} else {
tensOut = tens2Letters[tens];
// Retour du total
var userEntry;
alert(num2Letters(parseInt(userEntry, 10)));
Les explications
Nous allons maintenant analyser ce corrigé afin que vous le compreniez bien.
Le « squelette » du code
Un code doit toujours posséder ce qui peut être appelé un « squelette » autour duquel il
peut s’articuler, c’est-à-dire un code contenant les principales structures de votre script
(comme un objet, une boucle, une fonction, etc.) que nous allons pouvoir étoffer au fur et
à mesure. Dans notre cas, nous avons besoin d’une fonction qui fera la conversion des
nombres, ainsi que d’une boucle pour demander à l’utilisateur d’entrer un nouveau
nombre sans jamais s’arrêter (sauf si l’utilisateur le demande). Voici ce que ça donne :
function num2Letters(number) {
// « num2Letters » se lit « number to letters », le « 2 » est une abréviation
// souvent utilisée en programmation
// Notre fonction qui s'occupera de la conversion du nombre. Elle possède
// un argument lui permettant de recevoir les nombres en question.
}
/*
On « parse » (en base 10) la chaîne de caractères de l'utilisateur pour l'envoyer
ensuite
à notre fonction de conversion qui renverra le résultat à la fonction alert().
*/
alert(num2Letters(parseInt(userEntry, 10)));
}
Puis, nous déclarons les variables nécessaires à la bonne exécution de notre fonction :
function num2Letters(number) {
/*
Ci-dessous on déclare deux tableaux contenant les nombres en toutes lettres, un
tableau pour les unités et un autre pour les dizaines.
Le tableau des unités va du chiffre 1 à 19 afin de simplifier quelques
opérations lors de la conversion du nombre en lettres.
Vous comprendrez ce système par la suite.
*/
/*
Ci-dessous on calcule le nombre d'unités, de dizaines et de centaines à l'aide du
modulo.
Le principe est simple : si on prend 365 % 10 on obtient le reste de la division
par 10 qui est : 5. Voilà les unités.
Ensuite, sur 365 % 100 on obtient 65, on soustrait les unités à ce nombre 65 - 5
= 60, et on divise par 10 pour obtenir 6, voilà les dizaines !
Le principe est le même pour les centaines sauf qu'on ne soustrait pas seulement
les unités mais aussi les dizaines.
*/
Si ça paraît aussi simple, c’est parce que notre code a été bien pensé dès le début et
organisé de façon à pouvoir travailler le plus facilement possible. Il y a sûrement moyen
de faire mieux, mais ce code simplifie quand même grandement les choses, non ?
Maintenant, notre plus grande difficulté va être de se plier aux règles orthographiques
de la langue française !
Vous aurez sans doute remarqué que nos tableaux ne contiennent pas le chiffre « zéro ».
Nous allons l’ajouter à l’aide d’une condition :
function num2Letters(number) {
if (number === 0) {
}
}
if (number === 0) {
/*
Ci-dessous on calcule les unités. La dernière partie du code (après le +) ne
doit normalement pas vous poser de problèmes. Mais pour la condition
ternaire je pense que vous voyez assez peu son utilité. En fait, elle va
permettre d'ajouter « et- » à l'unité quand cette dernière vaudra 1 et que
les dizaines seront supérieures à 0 et différentes de 8. Pourquoi ? Tout
simplement parce qu'on ne dit pas « vingt-un » mais « vingt-et-un », cette
règle s'applique à toutes les dizaines sauf à « quatre-vingt-un » (d'où le
« tens !== 8 »).
*/
unitsOut = (units === 1 && tens > 0 && tens !== 8 ? 'et-' : '') +
units2Letters[units];
}
}
Afin de simplifier la lecture du code, nous avons placé toutes les conditions
ternaires entre parenthèses, même si normalement cela n’est pas vraiment
nécessaire.
if (number === 0) {
/*
La condition qui suit se charge de convertir les nombres allant de 11 à 19.
Pourquoi cette tranche de nombres ?
Parce qu'ils ne peuvent pas se décomposer (essayez de décomposer en toutes
lettres le nombre « treize », vous nous en direz des nouvelles), ils
nécessitent donc d'être mis un peu à part. Bref, leur conversion en lettres
s'effectue donc dans la partie concernant les dizaines. Ainsi,on va se
retrouver avec, par exemple,
« tensOut = 'treize'; » donc au final, on va effacer la variable
« unitsOut » vu qu'elle ne servira à rien.
*/
/*
La condition suivante va s'occuper des dizaines égales à 7 ou 9. Pourquoi ?
Eh bien un peu pour la même raison que la précédente condition : on retrouve
les nombres allant de 11 à 19. En effet, on dit bien « soixante-treize » et
« quatre-vingt-treize » et non pas « soixante-dix-trois » ou autre bêtise du
genre. Bref, cette condition est donc chargée, elle aussi, de convertir les
dizaines et les unités. Elle est aussi chargée d'ajouter la conjonction
« et- » si l'unité vaut 1, car on dit « soixante-et-onze » et non pas
« soixante-onze ».
*/
else {
tensOut = tens2Letters[tens];
}
// Dernière étape, « quatre-vingt » sans unité prend un « s » à
// la fin : « quatre-vingts »
}
}
Un peu complexe tout ça, n’est-ce pas ? Rassurez-vous, c’était le passage le plus
difficile. Attaquons-nous maintenant aux centaines, plus simples :
function num2Letters(number) {
if (number === 0) {
/*
La conversion des centaines se fait en une ligne et trois ternaires. Ces
trois ternaires se chargent d'afficher un chiffre si nécessaire avant
d'écrire « cent » (exemple : « trois-cents »), d'afficher ou non la chaîne
« cent » (s'il n'y a pas de centaines, on ne l'affiche pas, normal), et
enfin d'ajouter un « s » à la chaîne « cent » s'il n'y a ni dizaines, ni
unités et que les centaines sont supérieures à 1.
*/
if (number === 0) {
/*
Cette ligne de code retourne toutes les valeurs converties en les
concaténant les unes aux autres avec un tiret. Pourquoi y a-t-il besoin de
ternaires ? Parce que si l'on n'en met pas, alors on risque de retourner des
valeurs du genre « -quatre-vingt- » juste parce qu'il n'y avait pas de
centaines et d'unités.
*/
Conclusion
Si vous avez trouvé cet exercice difficile, rassurez-vous : il l’est. Il existe certes des
codes beaucoup plus évolués et compliqués, mais pour quelqu’un qui débute c’est déjà
beaucoup ! Si vous n’avez toujours pas tout compris, je ne peux que vous encourager à
relire les explications ou refaire l’exercice. Si, malgré tout, vous n’y arrivez pas,
consultez le site OpenClassrooms, dont le forum possède une rubrique consacrée au
JavaScript (https://openclassrooms.com/forum/categorie/javascript). Vous y trouverez
facilement de l’aide, pour peu que vous parveniez à expliquer correctement votre
problème.
Deuxième partie
Modeler les pages web
Le JavaScript est un langage qui permet de créer ce qu’on appelle des « pages
DHTML ». Ce terme désigne les pages web qui modifient elles-mêmes leur propre
contenu sans charger de nouvelle page. C’est cette modification de la structure d’une
page web que nous allons étudier dans cette partie.
10 Manipuler le code HTML :
les bases
Le DOM est donc une API qui s’utilise avec les documents XML et HTML, et qui va
nous permettre, via le JavaScript, d’accéder au code XML et/ou HTML d’un document.
C’est grâce au DOM que nous allons pouvoir modifier des éléments HTML (afficher ou
masquer un <div>, par exemple), en ajouter, en déplacer ou même en supprimer.
Petit historique
À l’origine, quand le JavaScript a été intégré dans les premiers navigateurs (Internet
Explorer et Netscape Navigator), le DOM n’était pas unifié, c’est-à-dire que les deux
navigateurs possédaient un DOM différent. Pour accéder à un élément HTML, la
manière de faire différait donc d’un navigateur à l’autre, ce qui obligeait les
développeurs web à coder différemment en fonction du navigateur. En somme, c’était un
peu la jungle.
Le W3C a mis de l’ordre dans tout ça et a publié une nouvelle spécification que nous
appellerons DOM-1 (pour DOM Level 1). Cette nouvelle spécification définit
clairement ce qu’est le DOM, et surtout comment un document HTML ou XML est
schématisé. Depuis lors, un document HTML ou XML est représenté sous la forme d’un
arbre, ou plutôt hiérarchiquement. Ainsi, l’élément<html> contient deux éléments
enfants : <head> et <body>, qui à leur tour contiennent d’autres éléments enfants.
La spécification DOM-2 a ensuite vu le jour. La grande nouveauté de cette version 2 est
l’introduction de la méthode getElementById() qui permet de récupérer un élément
HTML ou XML en connaissant son ID.
L’objet window
Avant de véritablement parler du document, c’est-à-dire de la page web, nous allons
nous intéresser à l’objet window. Il s’agit d’un objet global qui représente la fenêtre du
navigateur. C’est à partir de cet objet que le JavaScript est exécuté.
Si nous reprenons notre script « Hello World! » du début, nous avons :
<!DOCTYPE html>
<html>
<head>
<title>Hello World!</title>
</head>
<body>
<script>
alert('Hello world!');
</script>
</body>
</html>
Contrairement à ce qui a été dit précédemment, alert() n’est pas vraiment une
fonction. Il s’agit en réalité d’une méthode appartenant à l’objet window. Mais ce
dernier est dit implicite, c’est-à-dire qu’il est généralement inutile de le spécifier.
Ainsi, ces deux instructions produisent le même effet, à savoir ouvrir une boîte de
dialogue :
window.alert('Hello world!');
alert('Hello world!');
Puisqu’il n’est pas nécessaire de spécifier l’objet window, on ne le fait généralement
pas sauf si cela est indispensable, par exemple si on manipule des frames.
De même, lorsque vous déclarez une variable dans le contexte global de votre script,
cette dernière deviendra en réalité une propriété de l’objet window. Afin de vous
démontrer facilement cela, observez l’exemple suivant :
var text = 'Variable globale !';
})();
Une dernière chose importante qu’il vous faut mémoriser : toute variable non déclarée
(donc utilisée sans jamais écrire le mot-clé var) deviendra immédiatement une
propriété de l’objet window, et ce, quel que soit l’endroit où vous utilisez cette
variable ! Prenons un exemple simple :
(function() { // On utilise une IIFE pour « isoler » du code
text = 'Variable accessible !'; // Cette variable n'a jamais été déclarée
// et pourtant on lui attribue une valeur
})();
Notre variable a été utilisée pour la première fois dans une IIFE et pourtant nous y
avons accès depuis l’espace global ! Ce fonctionnement s’explique tout simplement
parce que le JavaScript va chercher à résoudre le problème que nous lui avons donné :
on lui demande d’attribuer une valeur à la variable text, il va donc chercher cette
variable mais ne la trouve pas, la seule solution pour résoudre le problème qui lui est
donné est alors d’utiliser l’objet window. Ce qui veut dire qu’en écrivant :
text = 'Variable accessible !';
le code sera interprété de cette manière si aucune variable accessible n’existe avec ce
nom :
window.text = 'Variable accessible !';
Le document
L’objet document est un sous-objet de window, l’un des plus utilisés. Et pour cause, il
représente la page web et plus précisément la balise <html>. C’est grâce à cet élément
que nous allons pouvoir accéder aux éléments HTML et les modifier. Voyons donc
comment naviguer dans le document.
<body>
<div>
<p>Un peu de texte <a>et un lien</a></p>
</div>
</body>
</html>
Le schéma est plutôt simple : l’élément <html> contient deux éléments, appelés enfants :
<head> et <body>. Pour ces deux enfants, <html> est l’élément parent. Chaque élément
est appelé nœud (node en anglais). L’élément <head> contient lui aussi deux enfants :
<meta> et <title>. <meta> ne contient pas d’enfant tandis que <title> en contient un,
nommé #text. Comme son nom l’indique, #text est un élément qui contient du texte.
Il est important de bien saisir cette notion : le texte présent dans une page web est vu par
le DOM comme un nœud de type #text. Dans le schéma précédent, l’exemple du
paragraphe qui contient du texte et un lien illustre bien cela :
<p>
Un peu de texte
<a>et un lien</a>
</p>
getElementById()
Cette méthode permet d’accéder à un élément en connaissant son ID qui est simplement
l’attribut id de l’élément. Elle fonctionne de cette manière :
<div id="myDiv">
<p>Un peu de texte <a>et un lien</a></p>
</div>
<script>
var div = document.getElementById('myDiv');
alert(div);
</script>
Il nous dit que div est un objet de type HTMLDivElement. En clair, c’est un élément
HTML qui se trouve être un <div>, ce qui nous montre que le script fonctionne
correctement.
getElementsByTagName()
Le nom de la méthode Elements s’écrit toujours avec un « s » final. C’est une
source fréquente d’erreurs.
Cette méthode permet de récupérer, sous la forme d’un tableau, tous les éléments de la
famille. Si, dans une page, on veut récupérer tous les <div>, il suffit de faire comme
ceci :
var divs = document.getElementsByTagName('div');
getElementsByName()
Cette méthode est semblable à getElementsByTagName() et permet de ne récupérer
que les éléments qui possèdent un attribut name que vous spécifiez. L’attribut name n’est
utilisé qu’au sein des formulaires, et est déprécié depuis la spécification HTML 5 dans
tout autre élément qu’un formulaire. Par exemple, vous pouvez vous en servir pour un
élément <input> mais pas pour un élément <map>.
Sachez aussi que cette méthode est dépréciée en XHTML mais qu’elle est standardisée
en HTML 5.
Ce sélecteur CSS stipule qu’on souhaite sélectionner les balises de type <span>
contenues dans les classes .item, elles-mêmes contenues dans un élément dont
l’identifiant est #menu.
Le principe est plutôt simple mais très efficace. Sachez que ces deux méthodes
supportent aussi les sélecteurs CSS 3, bien plus complets. Vous pouvez consulter leur
liste sur la spécification du W3C (http://www.w3.org/Style/css3-selectors-
updates/WD-css3-selectors-20010126.fr.html#selectors).
Voyons les particularités de ces deux méthodes. querySelector() renvoie le premier
élément trouvé correspondant au sélecteur CSS, tandis que querySelectorAll() va
renvoyer tous les éléments (sous forme de tableau) correspondant au sélecteur CSS
fourni. Prenons un exemple simple :
<div id="menu">
<div class="item">
<span>Élément 1</span>
<span>Élément 2</span>
</div>
<div class="publicite">
<span>Élément 3</span>
<span>Élément 4</span>
</div>
</div>
<div id="contenu">
<span>Introduction au contenu de la page...</span>
</div>
Maintenant, essayons le sélecteur CSS présenté plus haut : #menu .item span
Notion d’héritage
Nous avons vu qu’un élément <div> est un objet HTMLDivElement, mais un objet, en
JavaScript, peut appartenir à différents groupes. Ainsi, notre <div> est un
HTMLDivElement, qui est un sous-objet de HTMLElement, qui est lui-même un sous-
objet de Element, lequel est enfin un sous-objet de Node. Ce schéma est plus parlant :
L’objet Node apporte un certain nombre de propriétés et de méthodes qui pourront être
utilisées depuis un de ses sous-objets. En clair, les sous-objets héritent des propriétés et
méthodes de leurs objets parents. Voilà donc ce qu’on appelle l’héritage.
Les attributs
Via l’objet Element
Pour pouvoir interagir avec les attributs, l’objet Element fournit deux méthodes,
getAttribute() et setAttribute(), qui permettent respectivement de récupérer et
d’éditer un attribut. Le premier paramètre est le nom de l’attribut, et le second, dans le
cas de setAttribute() uniquement, est la nouvelle valeur à donner à l’attribut. Voici
un petit exemple :
<body>
<a id="myLink" href="http://www.un_lien_quelconque.com">Un lien modifié
dynamiquement</a>
<script>
var link = document.getElementById('myLink');
var href = link.getAttribute('href'); // On récupère l'attribut « href »
alert(href);
Nous commençons par récupérer l’élément myLink, puis nous lisons son attribut href
via getAttribute(). Ensuite, nous modifions la valeur de href avec
setAttribute().
Le lien pointe maintenant vers https://openclassrooms.com/.
<script>
var link = document.getElementById('myLink');
var href = link.href;
alert(href);
link.href = 'https://openclassrooms.com';
</script>
</body>
La classe
On peut penser que pour modifier l’attribut class d’un élément HTML, il suffit
d’utiliser element.class. Ceci n’est pas possible car le mot-clé class est réservé en
JavaScript, bien qu’il n’ait aucune utilité. À la place de class, il faudra utiliser
className.
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Le titre de la page</title>
<style>
.blue {
background: blue;
color: white;
}
</style>
</head>
<body>
<div id="myColoredDiv">
<p>Un peu de texte <a>et un lien</a></p>
</div>
<script>
document.getElementById('myColoredDiv').className = 'blue';
</script>
</body>
</html>
Dans cet exemple, on définit la classe CSS .blue à l’élément myColoredDiv, ce qui
fait que cet élément sera affiché avec un arrière-plan bleu et un texte blanc.
Le mot-clé for est également réservé en JavaScript (pour les boucles). Vous
ne pouvez donc pas modifier l’attribut HTML for d’un <label> en écrivant
element.for.
Il faudra utiliser element.htmlFor à la place.
Faites bien attention : si votre élément comporte plusieurs classes (par exemple,
<a class="external red u">) et que vous récupérez la classe avec className,
cette propriété ne retournera pas un tableau avec les différentes classes. Elle retournera
la chaîne « external red u », ce qui n’est pas vraiment le comportement souhaité. Il vous
faudra alors couper cette chaîne avec la méthode split()pour obtenir un tableau,
comme ceci :
var classes = document.getElementById('myLink').className;
var classesNew = [];
classes = classes.split(' ');
alert(classesNew);
// Retire une classe si elle est présente ou bien l'ajoute si elle est absente
div.classList.toggle('toggled-class');
alert(result);
Le contenu : innerHTML
La propriété innerHTML est spéciale et demande une petite introduction. Elle a été
créée par Microsoft pour les besoins d’Internet Explorer et a été normalisée au sein du
HTML 5. Bien que non normalisée pendant des années, elle est devenue un standard
parce que tous les navigateurs la supportaient déjà, et non l’inverse comme c’est
généralement le cas.
Récupérer du HTML
innerHTML permet de récupérer le code HTML enfant d’un élément sous forme de texte.
Ainsi, si des balises sont présentes, innerHTML les retournera sous forme de texte :
<body>
<div id="myDiv">
<p>Un peu de texte <a>et un lien</a></p>
</div>
<script>
var div = document.getElementById('myDiv');
alert(div.innerHTML);
</script>
</body>
Nous avons donc bien une boîte de dialogue qui affiche le contenu de myDiv, sous forme
de texte :
Si vous voulez ajouter du contenu et ne pas modifier celui déjà en place, il suffit
d’utiliser += à la place de l’opérateur d’affectation :
document.getElementById('myDiv').innerHTML += ' et <strong>une portion mise en
emphase</strong>.';
Voici toutefois une petite mise en garde : n’utilisez pas le += dans une boucle ! En effet,
innerHTML ralentit considérablement l’exécution du code si nous procédons de cette
manière. Il vaut donc mieux concaténer son texte dans une variable pour ensuite ajouter
le tout via innerHTML. Exemple :
var text = '';
while ( /* condition */ ) {
text += 'votre_texte'; // On concatène dans la variable « text »
}
Si un jour il vous prend l’envie d’ajouter une balise <script> à votre page par
le biais de la propriété innerHTML, sachez que cela ne fonctionne pas ! Il est
toutefois possible de créer cette balise par le biais de la méthode
createElement() que nous étudierons au prochain chapitre.
innerText et textContent
Penchons-nous maintenant sur deux propriétés analogues à innerHTML : innerText
pour Internet Explorer et textContent pour les autres navigateurs.
innerText
La propriété innerText a aussi été introduite dans Internet Explorer, mais
contrairement à sa propriété sœur innerHTML, elle n’a jamais été standardisée et n’est
pas supportée par tous les navigateurs. Internet Explorer (pour toute version antérieure à
la neuvième) ne supporte que cette propriété, mais pas la version standardisée que nous
verrons par la suite.
Le fonctionnement de innerText est le même que celui de innerHTML à la différence
que seul le texte est récupéré, et non les balises. C’est pratique pour récupérer du
contenu sans le balisage, voici un petit exemple :
<body>
<div id="myDiv">
<p>Un peu de texte <a>et un lien</a></p>
</div>
<script>
var div = document.getElementById('myDiv');
alert(div.innerText);
</script>
</body>
Ce qui donne bien « Un peu de texte et un lien », sans les balises :
textContent
La propriété textContent est la version standardisée de innerText. Elle est reconnue
par tous les navigateurs à l’exception des versions d’Internet Explorer antérieures à la
version 9. Le fonctionnement est évidemment le même.
Vous vous demandez sans doute comment créer un script qui fonctionne à la fois pour
Internet Explorer et les autres navigateurs ? C’est ce que nous allons voir dans les
sections suivantes.
Tester le navigateur
À l’aide d’une simple condition, il est possible de vérifier si le navigateur prend en
charge telle méthode ou telle propriété.
<body>
<div id="myDiv">
<p>Un peu de texte <a>et un lien</a></p>
</div>
<script>
var div = document.getElementById('myDiv');
var txt = '';
alert(txt);
</script>
</body>
Il suffit donc de tester par le biais d’une condition, si l’instruction fonctionne. Si
textContent ne fonctionne pas, nous utilisons innerText :
Cela dit, ce code est quand même très long et redondant. Il est possible de le raccourcir
de manière considérable :
txt = div.textContent || div.innerText || '';
Cet ouvrage n’abordera pas la prise charge des versions d’Internet Explorer
antérieures à la version 9. Nous avons fait une exception ici afin de vous
présenter de quelle manière vous pouvez gérer les problèmes de compatibilité.
Si vous souhaitez vérifier si un navigateur supporte une certaine technologie
HTML, CSS ou JavaScript, vous pouvez consulter le Mozilla Developer
Network (https://developer.mozilla.org/fr/) ou le site Can I use…
(http://caniuse.com/) qui sont des mines d’informations, maintenues par des
communautés actives.
En résumé
• Le DOM va servir à accéder aux éléments HTML présents dans un document afin de
les modifier et d’interagir avec eux.
• L’objet window est un objet global représentant la fenêtre du navigateur. document,
quant à lui, est un sous-objet de window et représente la page web. C’est grâce à lui
qu’on va pouvoir accéder aux éléments HTML de la page web.
• Les éléments de la page sont structurés comme un arbre généalogique, avec l’élément
<html> comme élément fondateur.
• Différentes méthodes, comme getElementById(), getElementsByTagName(),
querySelector() ou querySelectorAll(), sont disponibles pour accéder aux
éléments.
• Les attributs peuvent tous être modifiés grâce à setAttribute(). Certains éléments
possèdent des propriétés qui permettent de modifier ces attributs.
• La propriété innerHTML permet de récupérer ou de définir le code HTML présent à
l’intérieur d’un élément.
• De leur côté, textContent et innerText ne sont capables que de définir ou
récupérer du texte brut, sans aucunes balises HTML.
QCM
(http://odyssey.sdlm.be/javascript/26/partie2/chapitre1/qcm.htm)
Questionnaire didactique
(http://odyssey.sdlm.be/javascript/27/partie2/chapitre1/questionnaire.htm)
Modifier un élément
(http://odyssey.sdlm.be/javascript/28/partie2/chapitre1/linkhref.htm)
Utilisation de innerHTML
(http://odyssey.sdlm.be/javascript/30/partie2/chapitre1/innerhtml.htm)
11 Manipuler le code HTML :
les notions avancées
La propriété parentNode
La propriété parentNode permet d’accéder à l’élément parent d’un élément. Voici un
exemple :
<blockquote>
<p id="myP">Ceci est un paragraphe !</p>
</blockquote>
Admettons que nous devions accéder à myP et que pour une autre raison, nous voulions
aussi accéder à l’élément <blockquote>, qui est le parent de myP. Pour ce faire, il
suffit d’accéder à myP, puis à son parent grâce à parentNode :
var paragraph = document.getElementById('myP');
var blockquote = paragraph.parentNode;
nodeType et nodeName
Les propriétés nodeType et nodeName servent respectivement à vérifier le type et le
nom d’un nœud. nodeType retourne un nombre, qui correspond à un type de nœud. Le
tableau suivant liste les différents types possibles, ainsi que leurs numéros (les types
courants sont en gras) :
<body>
<div>
<p id="myP">Un peu de texte, <a>un lien</a> et <strong>une portion en
emphase</strong></p>
</div>
<script>
var paragraph = document.getElementById('myP');
var first = paragraph.firstChild;
var last = paragraph.lastChild;
alert(first.nodeName.toLowerCase());
alert(last.nodeName.toLowerCase());
</script>
</body>
</html>
Le premier enfant de <p> est un nœud textuel, alors que le dernier enfant est un élément
<strong>.
Si vous ne souhaitez récupérer que les enfants, qui sont considérés comme
des éléments HTML (et donc éviter les nœuds #text, par exemple), sachez
qu’il existe les propriétés firstElementChild et lastElementChild. Ainsi,
dans l’exemple précédent, la propriété firstElementChild renverrait
l’élément <a>.
Malheureusement, ces deux propriétés ne sont supportées qu’à partir de la
version 9 d’Internet Explorer.
nodeValue et data
Nous souhaitons à présent récupérer le texte du premier enfant et le texte contenu dans
l’élément <strong>.
Pour cela, vous pouvez utiliser la propriété nodeValue ou la propriété data. Si nous
réécrivons le code du script précédent, nous obtenons ceci :
var paragraph = document.getElementById('myP');
var first = paragraph.firstChild;
var last = paragraph.lastChild;
alert(first.nodeValue);
alert(last.firstChild.data);
childNodes
La propriété childNodes retourne un tableau contenant la liste des enfants d’un
élément. L’exemple suivant illustre son fonctionnement, de manière à récupérer le
contenu des éléments enfants :
<body>
<div>
<p id="myP">Un peu de texte <a>et un lien</a></p>
</div>
<script>
var paragraph = document.getElementById('myP');
var children = paragraph.childNodes;
}
</script>
</body>
nextSibling et previousSibling
nextSibling et previousSibling sont deux propriétés qui permettent d’accéder
respectivement au nœud suivant et au nœud précédent.
<body>
<div>
<p id="myP">Un peu de texte, <a>un lien</a> et <strong>une portion en
emphase</strong></p>
</div>
<script>
var paragraph = document.getElementById('myP');
var first = paragraph.firstChild;
var next = first.nextSibling;
<script>
var paragraph = document.getElementById('myP');
var child = paragraph.lastChild; // On prend le dernier enfant
while (child) {
child = child.previousSibling;
// À chaque tour de boucle, on prend l'enfant précédent
}
</script>
</body>
(Essayez le code : http://course.oc-static.com/ftp-
tutos/cours/javascript/part2/chap2/ex6.html)
Pour changer un peu, la boucle tourne « à l’envers », car nous récupérons d’abord le
dernier enfant, puis nous cheminons à reculons.
Tout comme pour firstChild et lastChild, sachez qu’il existe les propriétés
nextElementSibling et previousElementSibling qui permettent, elles aussi,
de ne récupérer que les éléments HTML. Ces deux propriétés ont les mêmes
problèmes de compatibilité que firstElementChild et lastElementChild.
En fait, les espaces entre les éléments tout comme les retours à la ligne sont considérés
comme des nœuds textuels (enfin, cela dépend des navigateurs) ! Ainsi, si nous
schématisons le premier code, nous obtenons ceci :
Création de l’élément
La création d’un élément se fait avec la méthode createElement(), un sous-objet de
l’objet racine, soit document dans la majorité des cas :
var newLink = document.createElement('a');
Nous créons ici un nouvel élément <a>. Il est créé, mais il n’est pas inséré dans le
document, il n’est donc pas visible. Cela dit, nous pouvons déjà travailler dessus, en lui
ajoutant des attributs ou même des événements (voir chapitre suivant).
Si vous travaillez dans une page web, l’élément racine sera toujours document,
sauf dans le cas des frames.
Insérer l’élément
On utilise la méthode appendChild() (« ajouter un enfant » en anglais) pour insérer
l’élément. Pour cela, nous devons connaître l’élément auquel nous allons ajouter
l’élément créé. Considérons donc le code suivant :
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Le titre de la page</title>
</head>
<body>
<div>
<p id="myP">Un peu de texte <a>et un lien</a></p>
</div>
</body>
</html>
Nous allons ajouter notre élément <a> dans l’élément <p> portant l’ID myP. Pour ce
faire, il suffit de récupérer cet élément et d’ajouter notre élément <a> via
appendChild() :
document.getElementById('myP').appendChild(newLink);
Une petite explication s’impose. Avant d’insérer notre élément <a>, la structure DOM
du document ressemble à ceci :
L’élément <a> existe, mais il n’est pas lié, un peu comme s’il était libre dans le
document : il n’est pas encore placé. Il s’agit ici de le placer comme enfant de l’élément
myP. La méthode appendChild() va alors déplacer notre élément <a> pour le placer en
tant que dernier enfant de myP :
L’élément est placé en tant que dernier enfant.
Cela signifie que appendChild() insérera toujours l’élément en tant que dernier enfant,
ce qui n’est pas toujours très pratique. Nous verrons plus tard comment insérer un
élément avant ou après un enfant donné.
newLink.appendChild(newLinkText);
L’insertion se fait ici aussi avec appendChild(), sur l’élément newLink. Afin d’y voir
plus clair, résumons le code :
<body>
<div>
<p id="myP">Un peu de texte <a>et un lien</a></p>
</div>
<script>
var newLink = document.createElement('a');
newLink.id = 'sdz_link';
newLink.href = 'http://www.openclassrooms.com';
newLink.title = 'Découvrez le site OpenClassrooms !';
newLink.setAttribute('tabindex', '10');
document.getElementById('myP').appendChild(newLink);
newLink.appendChild(newLinkText);
</script>
</body>
(Essayez le code : http://course.oc-static.com/ftp-
tutos/cours/javascript/part2/chap2/ex7.html)
Résultat obtenu
À noter que le fait d’insérer via appendChild() n’a aucune incidence sur l’ordre
d’exécution des instructions. Cela signifie que nous pouvons travailler sur les éléments
HTML et les nœuds textuels sans qu’ils soient au préalable insérés dans le document.
Par exemple, nous pourrions ordonner le code comme ceci :
var newLink = document.createElement('a');
var newLinkText = document.createTextNode("Le site OpenClassrooms");
newLink.id = 'sdz_link';
newLink.href = 'http://www.openclassrooms.com';
newLink.title = 'Découvrez OpenClassrooms !';
newLink.setAttribute('tabindex', '10');
newLink.appendChild(newLinkText);
document.getElementById('myP').appendChild(newLink);
Ici, nous commençons par créer les deux éléments (le lien et le nœud de texte), puis
nous affectons les variables au lien et nous lui ajoutons le nœud textuel. À ce stade,
l’élément HTML contient le nœud textuel, mais cet élément n’est pas encore inséré dans
le document :
L’élément HTML contient le nœud textuel,
mais cet élément n’est pas encore inséré dans le document.
appendChild() retourne une référence (voir plus loin pour plus de détails) pointant sur
l’objet qui vient d’être inséré. Cela peut servir dans le cas où vous n’avez pas déclaré
de variable intermédiaire lors du processus de création de votre élément. Par exemple,
le code suivant ne pose pas de problème :
var span = document.createElement('span');
document.body.appendChild(span);
En revanche, si vous retirez l’étape intermédiaire (la première ligne) pour gagner une
ligne de code, vous allez être embêtés pour modifier le contenu :
document.body.appendChild(document.createElement('span'));
Si on modifie la valeur de nick2, la valeur de nick1 reste inchangée : normal, les deux
variables sont bien distinctes.
Les références
Outre le passage par valeur, le JavaScript possède un « passage par référence ». En fait,
quand une variable est créée, sa valeur est mise en mémoire par l’ordinateur. Pour
pouvoir retrouver cette valeur, elle est associée à une adresse que seul l’ordinateur
connaît et manipule (on ne s’en occupe pas).
Quand on passe une valeur par référence, on transmet l’adresse de la valeur, ce qui va
permettre d’avoir deux variables qui pointent sur une même valeur. Malheureusement,
un exemple théorique d’un passage par référence n’est pas vraiment envisageable à ce
stade du tutoriel, il faudra attendre d’aborder le chapitre 17 sur la création d’objets.
Cela dit, quand on manipule une page web avec le DOM, on est confronté à des
références, tout comme dans le chapitre suivant sur les événements.
newLink.id = 'sdz_link';
newLink.href = 'http://www.openclassrooms.com';
newLink.appendChild(newLinkText);
document.getElementById('myP').appendChild(newLink);
sdzLink.href = 'http://www.openclassrooms.com/forum.html';
La variable newLink contient en réalité une référence vers l’élément <a> qui a été créé.
newLink ne contient pas l’élément, il contient une adresse qui pointe vers ce fameux
élément <a>. Une fois que l’élément HTML est inséré dans la page, on peut y accéder de
nombreuses autres façons, comme avec getElementById(). Quand on accède à un
élément via getElementById(), on le fait aussi au moyen d’une référence.
Ce qu’il faut retenir de tout ça, c’est que les objets du DOM sont toujours accessibles
par référence, et c’est la raison pour laquelle ce code ne fonctionne pas :
var newDiv1 = document.createElement('div');
var newDiv2 = newDiv1; // On tente de copier le <div>
En effet, newDiv2 contient une référence qui pointe vers le même <div> que
newDiv1. Mais comment dupliquer un élément alors ? En le clonant, comme nous allons
le voir maintenant !
// Et attention, l'élément est cloné, mais pas « inséré » tant qu'on n'a
// pas appelé appendChild() :
paragraph1.parentNode.appendChild(paragraph2);
Une chose très importante à retenir, bien qu’elle ne vous concerne qu’au
chapitre suivant, est que la méthode cloneNode() ne copie malheureusement
pas les événements associés et créés avec le DOM (avec
addEventListener()), même avec un paramètre à true. Pensez bien à cela !
<script>
var link = document.querySelector('a');
var newLabel = document.createTextNode('et un hyperlien');
link.replaceChild(newLabel, link.firstChild);
</script>
</body>
Supprimer un élément
Pour insérer un élément, nous utilisons appendChild(). Pour en supprimer un, nous
utilisons removeChild(). Cette méthode prend en paramètre le nœud enfant à retirer. Si
on se calque sur le code HTML de l’exemple précédent, le script ressemble à ceci :
var link = document.querySelector('a');
link.parentNode.removeChild(link);
À noter que la méthode removeChild() retourne l’élément supprimé, ce qui veut dire
qu’il est parfaitement possible de supprimer un élément HTML pour ensuite le
réintégrer à l’endroit souhaité dans le DOM :
var link = document.querySelector('a');
document.body.appendChild(oldLink);
// On réintègre ensuite l'élément supprimé où on veut et quand on veut
Autres actions
Vérifier la présence d’éléments enfants
Pour vérifier la présence d’éléments enfants, vous utiliserez hasChildNodes() sur
l’élément de votre choix. Si ce dernier possède au moins un enfant, la méthode renverra
true :
<div>
<p id="myP">Un peu de texte <a>et un lien</a></p>
</div>
<script>
var paragraph = document.querySelector('p');
<script>
var paragraph = document.querySelector('p');
var emphasis = document.createElement('em'),
emphasisText = document.createTextNode(' en emphase légère ');
emphasis.appendChild(emphasisText);
paragraph.insertBefore(emphasis, paragraph.lastChild);
</script>
Algorithme
Pour insérer après un élément, nous allons tout d’abord récupérer l’élément parent. Ceci
est logique puisque l’insertion de l’élément va se faire grâce à appendChild() ou
insertBefore(). Ainsi, pour ajouter notre élément après le dernier enfant, il suffit
d’appliquer appendChild(). En revanche, si l’élément après lequel nous voulons
insérer notre élément n’est pas le dernier, nous utiliserons insertBefore() en ciblant
l’enfant suivant, avec nextSibling :
function insertAfter(newElement, afterElement) {
var parent = afterElement.parentNode;
if (parent.lastChild === afterElement) {
// Si le dernier élément est le même que l'élément après lequel
// on veut insérer, il suffit de faire appendChild()
parent.appendChild(newElement);
} else { // Dans le cas contraire, on fait un insertBefore() sur
// l'élément suivant
parent.insertBefore(newElement, afterElement.nextSibling);
}
}
Exercice 1
Nous vous proposons de recréer « du texte » mélangé à divers éléments tels que des
éléments <a> et <strong>. C’est assez simple, mais veillez à ne pas vous emmêler les
pinceaux avec tous les nœuds textuels !
<div id="divTP1">
Le <strong>World Wide Web Consortium</strong>, abrégé par le sigle
<strong>W3C</strong>, est un
<a href="http://fr.wikipedia.org/wiki/Organisme_de_normalisation"
title="Organisme de normalisation">organisme de standardisation</a> à but non
lucratif chargé de promouvoir la compatibilité des technologies du <a
href="http://fr.wikipedia.org/wiki/World_Wide_Web" title="World Wide Web">World Wide
Web</a>.
</div>
http://odyssey.sdlm.be/javascript/31/partie2/chapitre2/mini-tp-1.htm
Corrigé
// On crée l'élément conteneur
var mainDiv = document.createElement('div');
mainDiv.id = 'divTP1';
w3cStrong1.appendChild(textNodes[1]);
w3cStrong2.appendChild(textNodes[3]);
orgLink.href = 'http://fr.wikipedia.org/wiki/Organisme_de_normalisation';
orgLink.title = 'Organisme de normalisation';
orgLink.appendChild(textNodes[5]);
wwwLink.href = 'http://fr.wikipedia.org/wiki/World_Wide_Web';
wwwLink.title = 'World Wide Web';
wwwLink.appendChild(textNodes[7]);
Exercice 2
<div id="divTP2">
<p>Langages basés sur ECMAScript :</p>
<ul>
<li>JavaScript</li>
<li>JScript</li>
<li>ActionScript</li>
<li>EX4</li>
</ul>
</div>
Il serait assez fastidieux de créer quatre éléments <li> « à la main »… Utilisez plutôt
une boucle for et pensez à utiliser un tableau pour définir les éléments textuels.
http://odyssey.sdlm.be/javascript/32/partie2/chapitre2/mini-tp-2.htm
Corrigé
// On crée le paragraphe
var paragraph = document.createElement('p');
var paragraphText = document.createTextNode('Langages basés sur ECMAScript :');
paragraph.appendChild(paragraphText);
uItem.appendChild(languages[i]);
uList.appendChild(uItem);
}
<dl>
<dt>JavaScript</dt>
<dd>JavaScript est un langage de programmation de scripts surtout utilisé
dans les pages web interactives mais aussi coté serveur.</dd>
<dt>JScript</dt>
<dd>JScript est le nom générique de plusieurs implémentations d'ECMAScript 3
créées par Microsoft.</dd>
<dt>ActionScript</dt>
<dd>ActionScript est le langage de programmation utilisé au sein
d'applications clientes (Adobe Flash, Adobe Flex) et serveur (Flash media server,
JRun, Macromedia Generator).</dd>
<dt>EX4</dt>
<dd>ECMAScript for XML (E4X) est une extension XML au langage ECMAScript.
</dd>
</dl>
</div>
http://odyssey.sdlm.be/javascript/33/partie2/chapitre2/mini-tp-3.htm
Corrigé
// On crée l'élément conteneur
var mainDiv = document.createElement('div');
mainDiv.id = 'divTP3';
// On crée le paragraphe
var paragraph = document.createElement('p');
var paragraphText = document.createTextNode('Langages basés sur ECMAScript :');
paragraph.appendChild(paragraphText);
defTermText = document.createTextNode(languages[i].t);
defDefnText = document.createTextNode(languages[i].d);
defTerm.appendChild(defTermText);
defDefn.appendChild(defDefnText);
defList.appendChild(defTerm);
defList.appendChild(defDefn);
}
Exercice 4
Un peu plus corsé… quoique. Ici, la difficulté réside dans le nombre important
d’éléments à imbriquer les uns dans les autres. Procédez méthodiquement pour ne pas
vous tromper.
<div id="divTP4">
<form enctype="multipart/form-data" method="post" action="upload.php">
<fieldset>
<legend>Uploader une image</legend>
Corrigé
form.action = 'upload.php';
form.enctype = 'multipart/form-data';
form.method = 'post';
legend.appendChild(legendText);
fieldset.appendChild(legend);
fieldset.appendChild(center);
form.appendChild(fieldset);
input.type = 'file';
input.id = 'inputUpload';
input.name = input.id;
submit.type = 'submit';
submit.value = 'Envoyer';
label.htmlFor = 'inputUpload';
label.appendChild(labelText);
center.appendChild(label);
center.appendChild(input);
center.appendChild(br);
center.appendChild(br.cloneNode(false)); // On clone, pour mettre un deuxième
// <br />
center.appendChild(submit);
En résumé
• Une fois qu’on a accédé à un élément, on peut naviguer vers d’autres éléments avec
parentNode, previousSibling et nextSibling. Il est aussi possible de récupérer
des informations sur le nom des éléments et leur contenu.
• Pour ajouter un élément, il faut d’abord le créer, puis lui adjoindre des attributs et
enfin l’insérer à l’endroit voulu au sein du document.
• Outre le passage par valeur, le JavaScript possède un passage par référence qui est
fréquent lorsqu’on manipule le DOM. C’est cette histoire de référence qui nous
oblige à utiliser une méthode telle que cloneNode() pour dupliquer un élément. En
effet, copier la variable qui pointe vers cet élément ne sert à rien.
• Si appendChild() est particulièrement pratique, insertBefore() l’est tout autant
pour insérer un élément avant un autre. Créer une fonction insertAfter() est assez
simple et peut faire gagner du temps.
QCM
(http://odyssey.sdlm.be/javascript/35/partie2/chapitre2/qcm.htm)
Questionnaire fléché
(http://odyssey.sdlm.be/javascript/36/partie2/chapitre2/questionnaire.htm)
Modifier un tableau
(http://odyssey.sdlm.be/javascript/38/partie2/chapitre2/table.htm)
La théorie
Liste des événements
Il existe de nombreux événements, tous plus ou moins utiles. Parmi les événements utiles
que nous n’aborderons pas, sachez qu’il en existe des spécifiques pour les plates-
formes mobiles (smartphones, tablettes, etc.).
Voici la liste des principaux événements, ainsi que les actions à effectuer pour qu’ils se
déclenchent :
Toutefois, ce n’est pas tout, il existe aussi deux événements spécifiques à l’élément
<form>, que voici :
Tout cela est pour le moment très théorique, nous ne faisons que vous lister quelques
événements existants. Mais nous allons rapidement apprendre à les utiliser, après un
dernier point concernant ce qu’on appelle le « focus ».
Le focus
Le focus (http://en.wikipedia.org/wiki/Focus_%28computing%29) définit ce qui peut
être appelé le « ciblage » d’un élément. Lorsqu’un élément est ciblé, il va recevoir tous
les événements de votre clavier. Un exemple simple est d’utiliser un <input> de type
text : si vous cliquez dessus, alors l’élément input possède le focus, autrement dit il
est ciblé. Si vous appuyez sur des touches de votre clavier, vous verrez les caractères
correspondants s’afficher dans l’élément input en question.
Le focus peut s’appliquer à de nombreux éléments. Ainsi, si vous appuyez sur la touche
Tabulation de votre clavier alors que vous êtes sur une page web, l’élément ciblé ou
sélectionné recevra tout ce que vous tapez sur votre clavier. Par exemple, si vous avez
un lien ciblé et que vous appuyez sur la touche Entrée de votre clavier, vous serez
redirigés vers l’URL contenue dans ce lien.
La pratique
Utiliser les événements
Nous allons à présent passer à la pratique. Dans un premier temps, il s’agit uniquement
de vous faire découvrir à quoi sert tel ou tel événement et comment il réagit. Nous
allons donc voir comment les utiliser sans le DOM, ce qui est considérablement plus
limité.
Commençons par l’événement click sur un simple <span> :
<span onclick="alert('Hello !');">Cliquez-moi !</span>
Le mot-clé this
Normalement, ce mot-clé n’est pas censé vous servir à ce stade, mais il est toujours bon
de le connaître pour les événements. Il s’agit d’une propriété pointant sur l’objet en
cours d’utilisation. Si vous utilisez ce mot-clé lorsqu’un événement est déclenché,
l’objet pointé sera l’élément qui a déclenché l’événement. Voici un exemple :
<span onclick="alert('Voici le contenu de l\'élément que vous avez cliqué :
\n\n' + this.innerHTML);">Cliquez-moi !</span>
Il s’agit d’une vieille méthode qui permet d’insérer du JavaScript directement dans
l’attribut href de votre lien juste en ajoutant javascript: au début de l’attribut. Cette
technique est désormais obsolète et nous vous déconseillons de l’utiliser. Voici une
méthode alternative :
<a href="#" onclick="alert('Vous avez cliqué !'); return false;">Cliquez-moi !
</a>
Vous savez maintenant que l’utilisation de javascript: dans les liens est
prohibée. Cependant, gardez bien à l’esprit que l’utilisation d’un lien
uniquement pour le déclenchement d’un événement click n’est pas une bonne
chose. Utilisez plutôt une balise <button> à laquelle vous aurez retiré le style
CSS. La balise <a>, quant à elle, est conçue pour rediriger vers une page web
et non pas pour servir exclusivement de déclencheur !
<script>
element.onclick = function() {
alert("Vous m'avez cliqué !");
};
</script>
Voilà tout pour les événements DOM-0, nous pouvons maintenant passer au cœur des
événements : le DOM-2 et l’objet Event
(https://developer.mozilla.org/en/DOM/event).
Le DOM-2
Vous vous demandez peut-être pourquoi le DOM-2 et non pas le DOM-0, voire pas de
DOM du tout ? Pour la méthode sans le DOM, la réponse est simple : on ne peut pas y
utiliser l’objet Event qui est pourtant une mine d’informations sur l’événement
déclenché. Il est donc conseillé de mettre cette méthode de côté dès maintenant (nous
vous l’avons présentée uniquement pour que vous sachiez la reconnaître).
En ce qui concerne le DOM-0, il a deux problèmes majeurs : il est vieux et il ne permet
pas de créer plusieurs fois le même événement.
Le DOM-2, en revanche, permet la création multiple d’un même événement et gère
également l’objet Event. Pour autant, faut-il constamment utiliser le DOM-2 ?
Cela est très fortement conseillé surtout lorsque vous en viendrez à utiliser des
bibliothèques au sein de vos sites web. Si ces dernières ajoutent des événements DOM-
0 à vos éléments HTML, vous aurez alors deux problèmes.
• Ce sont de mauvaises bibliothèques, elles n’ont pas à faire ça. Nous vous conseillons
d’en trouver d’autres, le Web n’en manque pas.
• Les événements de votre propre code seront écrasés ou bien vous écraserez ceux des
bibliothèques. Dans les deux cas, cela s’avère assez gênant.
Le DOM-2
Comme pour les autres interfaces événementielles, voici un exemple avec l’événement
click :
<script>
var element = document.getElementById('clickme');
element.addEventListener('click', function() {
alert("Vous m'avez cliqué !");
});
</script>
element.addEventListener('click', myFunction);
<script>
element.removeEventListener('click', myFunction);
// On supprime l'événement en lui repassant les mêmes paramètres
Toute suppression d’événement avec le DOM-2 se fait avec les mêmes paramètres
utilisés lors de sa création ! Cependant, cela ne fonctionne pas aussi facilement avec les
fonctions anonymes ! Tout événement DOM-2 créé avec une fonction anonyme est
particulièrement complexe à supprimer, car il faut posséder une référence vers la
fonction concernée, ce qui n’est généralement pas le cas avec une fonction anonyme.
Si nous attribuons une fonction à l’événement click de chacun de ces deux éléments et
que nous cliquons sur le texte, quel événement va se déclencher en premier à votre
avis ?
Notre réponse se trouve dans les phases de capture et de bouillonnement. Si vous
décidez d’utiliser la capture, alors l’événement du <div> se déclenchera en premier,
puis viendra l’événement du <span>. En revanche, si vous utilisez le bouillonnement, ce
sera d’abord l’événement du <span> qui se déclenchera, puis celui du <div>.
Voici un petit code qui met en pratique l’utilisation de ces deux phases :
<div id="capt1">
<span id="capt2">Cliquez-moi pour la phase de capture.</span>
</div>
<div id="boul1">
<span id="boul2">Cliquez-moi pour la phase de bouillonnement.</span>
</div>
<script>
var capt1 = document.getElementById('capt1'),
capt2 = document.getElementById('capt2'),
boul1 = document.getElementById('boul1'),
boul2 = document.getElementById('boul2');
capt1.addEventListener('click', function() {
alert("L'événement du div vient de se déclencher.");
}, true);
capt2.addEventListener('click', function() {
alert("L'événement du span vient de se déclencher.");
}, true);
boul1.addEventListener('click', function() {
alert("L'événement du div vient de se déclencher.");
}, false);
boul2.addEventListener('click', function() {
alert("L'événement du span vient de se déclencher.");
}, false);
</script>
L’objet Event
Maintenant que nous avons vu comment créer et supprimer des événements, nous
pouvons passer à l’objet Event (https://developer.mozilla.org/en/DOM/event).
Il est important de préciser que l’objet Event peut se récupérer dans un argument autre
que e. En effet, vous pouvez très bien le récupérer dans un argument nommé test,
hello, ou autre… Après tout, l’objet Event est tout simplement passé en référence à
l’argument de votre fonction, ce qui vous permet de choisir le nom que vous souhaitez.
<script>
var clickme = document.getElementById('clickme');
clickme.addEventListener('click', function(e) {
e.target.innerHTML = 'Vous avez cliqué !';
});
</script>
<div id="parent1">
Parent
<div id="child1">Enfant N°1</div>
<div id="child2">Enfant N°2</div>
</div>
<script>
var parent1 = document.getElementById('parent1'),
result = document.getElementById('result');
parent1.addEventListener('mouseover', function(e) {
result.innerHTML = "L'élément déclencheur de l'événement \
"mouseover\" possède l'ID : " + e.target.id;
});
</script>
Cette propriété n’est pas supportée par les versions d’Internet Explorer
antérieures à la version 9.
<script>
var position = document.getElementById('position');
document.addEventListener('mousemove', function(e) {
position.innerHTML = 'Position X : ' + e.clientX + 'px<br />
Position Y : ' + e.clientY + 'px';
});
</script>
<div id="parent1">
Parent N°1<br /> Mouseover sur l'enfant
<div id="child1">Enfant N°1</div>
</div>
<div id="parent2">
Parent N°2<br /> Mouseout sur l'enfant
<div id="child2">Enfant N°2</div>
</div>
<script>
var child1 = document.getElementById('child1'),
child2 = document.getElementById('child2'),
result = document.getElementById('result');
child1.addEventListener('mouseover', function(e) {
result.innerHTML = "L'élément quitté juste avant que le curseur n'entre sur
l'enfant n°1 est : " + e.relatedTarget.id;
});
child2.addEventListener('mouseout', function(e) {
result.innerHTML = "L'élément survolé juste après que le curseur ait quitté
l'enfant n°2 est : " + e.relatedTarget.id;
});
</script>
Cependant, faites bien attention avec ces deux événements : toutes les touches retournant
un caractère retourneront un caractère majuscule, que la touche Maj soit pressée ou non.
L’événement keypress, quant à lui, est d’une toute autre utilité : il sert uniquement à
capter les touches qui écrivent un caractère. Oubliez donc les touches Ctrl, Alt et autres
qui n’affichent pas de caractère. Cet événement vous sera utile lorsque vous voudrez
détecter les combinaisons de touches. Ainsi, si vous utilisez la combinaison de touches
Maj+A, l’événement keypress détectera bien un A majuscule, là où les événements
keyup et keydown se seraient déclenchés deux fois (une fois pour la touche Maj et une
seconde fois pour la touche A).
Pour récupérer les caractères, vous pouvez utiliser l’une des trois propriétés permettant
de fournir une valeur : keyCode, charCode et which. Ces propriétés renvoient chacune
un code ASCII
(http://fr.wikipedia.org/wiki/American_Standard_Code_for_Information_Interchange)
correspondant à la touche pressée.
Cependant, la propriété keyCode est amplement suffisante dans tous les cas, comme
vous pouvez le constater dans l’exemple qui suit :
<p>
<input id="field" type="text" />
</p>
<table>
<tr>
<td>keydown</td>
<td id="down"></td>
</tr>
<tr>
<td>keypress</td>
<td id="press"></td>
</tr>
<tr>
<td>keyup</td>
<td id="up"></td>
</tr>
</table>
<script>
var field = document.getElementById('field'),
down = document.getElementById('down'),
press = document.getElementById('press'),
up = document.getElementById('up');
document.addEventListener('keydown', function(e) {
down.innerHTML = e.keyCode;
});
document.addEventListener('keypress', function(e) {
press.innerHTML = e.keyCode;
});
document.addEventListener('keyup', function(e) {
up.innerHTML = e.keyCode;
});
</script>
Cette méthode est donc conçue pour convertir les valeurs ASCII en caractères lisibles.
Faites donc bien attention à n’utiliser cette méthode qu’avec un événement keypress
afin d’éviter d’afficher, par exemple, le caractère d’un code correspondant à la touche
Ctrl, cela ne fonctionnera pas !
Pour terminer, voici un court exemple :
String.fromCharCode(84, 101, 115, 116); // Affiche : Test
Bloquer l’action par défaut de certains événements
Nous avons vu précédemment qu’il est possible de bloquer l’action par défaut de
certains événements, comme la redirection d’un lien vers une page web. Sans le DOM-
2, cette opération était très simple vu qu’il suffisait d’écrire return false;. Avec
l’objet Event, c’est quasiment tout aussi simple : il suffit d’appeler la méthode
preventDefault().
Reprenons l’exemple que nous avions utilisé pour les événements sans le DOM et
utilisons donc cette méthode :
<a id="link" href="http://www.openclassrooms.com">Cliquez-moi !</a>
<script>
var link = document.getElementById('link');
link.addEventListener('click', function(e) {
e.preventDefault(); // On bloque l'action par défaut de cet événement
alert('Vous avez cliqué !');
});
</script>
Le problème
Plutôt que de tenter une explication, vous allez pouvoir le constater par vous-même.
Considérez donc le code HTML et le code CSS qui suivent :
<div id="myDiv">
<div>Texte 1</div>
<div>Texte 2</div>
<div>Texte 3</div>
<div>Texte 4</div>
</div>
<div id="results"></div>
#myDiv, #results {
margin: 50px;
}
#myDiv {
padding: 10px;
width: 200px;
text-align: center;
background-color: #000;
}
#myDiv div {
margin: 10px;
background-color: #555;
}
Maintenant, voyons ce que nous souhaitons obtenir. Notre but ici est de faire en sorte de
détecter quand le curseur entre sur notre élément #myDiv et quand il en ressort. Vous
allez donc penser qu’il n’y a rien de plus facile et vous lancer dans un code de ce
genre :
var myDiv = document.getElementById('myDiv'),
results = document.getElementById('results');
myDiv.addEventListener('mouseover', function() {
results.innerHTML += "Le curseur vient d'entrer.<br />";
});
myDiv.addEventListener('mouseout', function() {
results.innerHTML += "Le curseur vient de sortir.<br />";
});
Voici donc notre problème : les enfants héritent des propriétés des événements
susnommés appliqués aux éléments parents. Ainsi, lorsque vous déplacez votre curseur
depuis le <div>#myDiv jusqu’à un <div> enfant, vous allez déclencher l’événement
mouseout sur #myDiv et l’événement mouseover sur le <div> enfant.
La solution
Afin de pallier ce problème, il existe une solution assez complexe. Vous souvenez-vous
de la propriété relatedTarget abordée dans ce chapitre ? Elle va permettre de
détecter quel est l’élément vers lequel le curseur se dirige ou de quel élément il
provient.
Ainsi, nous avons deux cas de figure :
• dans le cas de l’événement mouseover, nous devons détecter la provenance du
curseur. S’il vient d’un enfant de #myDiv, le code de l’événement ne devra pas être
exécuté. S’il provient d’un élément extérieur à #myDiv, alors l’exécution du code
peut s’effectuer ;
• dans le cas de mouseout, le principe est similaire, si ce n’est que nous devons ici
détecter la destination du curseur. S’il s’agit d’un enfant de #myDiv, le code de
l’événement n’est pas exécuté, sinon il s’exécutera sans problème.
Mettons cela en pratique avec l’événement mouseover pour commencer. Voici le code
d’origine :
myDiv.addEventListener('mouseover', function() {
results.innerHTML += "Le curseur vient d'entrer.";
});
Nous devons savoir si l’élément en question est un enfant direct de myDiv ou non. Pour
ce faire, il convient de remonter tout le long de ses éléments parents jusqu’à tomber soit
sur myDiv, soit sur l’élément <body> qui désigne l’élément HTML le plus haut dans
notre document. Il va donc nous falloir une boucle while :
myDiv.addEventListener('mouseover', function(e) {
});
Ainsi, nous retrouverons dans notre variable relatedTarget le premier élément trouvé
qui correspond à nos critères, c’est-à-dire soit myDiv, soit <body>. Il nous suffit alors
d’insérer une condition qui exécutera le code de notre événement uniquement dans le
cas où la variable relatedTarget ne pointe pas sur l’élément myDiv :
myDiv.addEventListener('mouseover', function(e) {
if (relatedTarget != myDiv) {
results.innerHTML += "Le curseur vient d'entrer.";
}
});
Cependant, il reste encore un petit cas de figure qui n’a pas été géré et qui peut être
source de problèmes. Comme vous le savez, la balise <body> ne couvre pas forcément
la page web complète de votre navigateur, ce qui fait que votre curseur peut provenir
d’un élément situé encore plus haut que cette balise. Cet élément correspond à la balise
<html> – soit l’élément document en JavaScript –, il nous faut donc faire une légère
modification afin de bien préciser que si le curseur provient de document, il ne peut
forcément pas provenir de myDiv :
myDiv.addEventListener('mouseover', function(e) {
if (relatedTarget != myDiv) {
results.innerHTML += "Le curseur vient d'entrer.";
}
});
if (relatedTarget != myDiv) {
results.innerHTML += "Le curseur vient de sortir.<br />";
}
});
En résumé
• Les événements sont utilisés pour appeler une fonction à partir d’une action produite
ou non par l’utilisateur.
• Différents événements existent pour détecter certaines actions comme le clic, le
survol, la frappe au clavier et le contrôle des champs de formulaires.
• Le DOM-0 est l’ancienne manière de capturer des événements. Le DOM-2 introduit
l’objet Event et la fameuse méthode addEventListener().
• L’objet Event permet de récolter toutes sortes d’informations se rapportant à
l’événement déclenché : son type, depuis quel élément il a été déclenché, la position
du curseur, les touches frappées… Il est aussi possible de bloquer l’action d’un
événement avec preventDefault().
• Parfois, un événement appliqué sur un parent se propage à ses enfants. Cet héritage
des événements peut provoquer des comportements inattendus.
QCM
(http://odyssey.sdlm.be/javascript/43/partie2/chapitre3/qcm.htm)
Utilisation de addEventListener()
(http://odyssey.sdlm.be/javascript/44/partie2/chapitre3/addeventlistener.htm)
Après l’étude des événements, il est temps de passer aux formulaires ! Ici commence
l’interaction avec l’utilisateur grâce aux nombreuses propriétés et méthodes dont sont
dotés les éléments HTML utilisés dans les formulaires.
Les propriétés
Les formulaires sont simples à utiliser, mais quelques propriétés élémentaires doivent
toutefois être mémorisées.
Comme vous le savez déjà, il est possible d’accéder à n’importe quelle propriété d’un
élément HTML simplement en tapant son nom. Il en va de même pour des propriétés
spécifiques aux éléments d’un formulaire comme value, disabled, checked, etc. Nous
allons voir ici comment utiliser ces propriétés spécifiques aux formulaires.
Un classique : value
Commençons par la propriété la plus connue et la plus utilisée : value. Pour rappel,
cette propriété permet de définir une valeur pour différents éléments d’un formulaire
comme les <input>, les <button>, etc. Son fonctionnement est des plus simples : on lui
assigne une valeur (une chaîne de caractères ou un nombre qui sera alors converti
implicitement) et elle est immédiatement affichée sur l’élément HTML. Exemple :
<input id="text" type="text" size="60" value="Vous n'avez pas le focus !" />
<script>
var text = document.getElementById('text');
text.addEventListener('focus', function(e) {
e.target.value = "Vous avez le focus !";
});
text.addEventListener('blur', function(e) {
e.target.value = "Vous n'avez pas le focus !";
});
</script>
(Essayez le code : http://course.oc-static.com/ftp-
tutos/cours/javascript/part2/chap4/ex1.html)
Par aileurs, cette propriété s’utilise aussi avec un élément <textarea>. En effet, en
HTML, il est courant de placer du texte dans un <textarea> en écrivant :
<textarea>Et voilà du texte !</textarea>
En JavaScript, on est alors souvent tenté d’utiliser innerHTML pour récupérer le contenu
du <textarea>, ce qui ne fonctionne pas. Il convient donc d’utiliser value à la place.
<script>
var text = document.getElementById('text');
text.disabled = true;
</script>
<script>
function check() {
var inputs = document.getElementsByTagName('input'),
inputsLength = inputs.length;
function check() {
var inputs = document.querySelectorAll('input[type=radio]:checked'),
inputsLength = inputs.length;
Toutes les vérifications concernant le type du champ et le fait qu’il soit coché ou non
s’effectuent au niveau de querySelectorAll(). On peut ainsi supprimer l’ancienne
condition.
<script>
var list = document.getElementById('list');
list.addEventListener('change', function() {
});
</script>
Maintenant revenons sur deux événements : submit et reset, encore les mêmes noms !
Il est inutile de vous expliquer quand l’un et l’autre se déclenchent, cela paraît évident.
Cependant, il est important d’ajouter une chose : envoyer un formulaire avec la méthode
submit() du JavaScript ne déclenchera jamais l’événement submit ! Voici un exemple
complet :
<form id="myForm">
<input type="text" value="Entrez un texte" />
<br /><br />
<input type="submit" value="Submit !" />
<input type="reset" value="Reset !" />
</form>
<script>
var myForm = document.getElementById('myForm');
myForm.addEventListener('submit', function(e) {
alert('Vous avez envoyé le formulaire !\n\nMais celui-ci a été bloqué pour
que vous ne changiez pas de page.');
e.preventDefault();
});
myForm.addEventListener('reset', function(e) {
alert('Vous avez réinitialisé le formulaire !');
});
</script>
(Essayez le code : http://course.oc-static.com/ftp-
tutos/cours/javascript/part2/chap4/ex4.html)
Par ailleurs, cet événement est bien entendu utilisable sur n’importe quel input dont
l’état peut changer, par exemple une checkbox ou un <input type="file" />.
N’allez surtout pas croire que cet événement est réservé seulement aux champs de texte !
En résumé
• La propriété value s’emploie sur la plupart des éléments de formulaire pour en
récupérer la valeur.
• Les listes déroulantes fonctionnent différemment, puisqu’il faut d’abord récupérer
l’index de l’élément sélectionné avec selectedIndex.
• Les méthodes focus() et blur() permettent de donner ou de retirer le focus à un
élément de formulaire.
• Attention à l’événement onchange, il ne fonctionne pas toujours comme son nom le
suggère, en particulier pour les champs de texte.
QCM
(http://odyssey.sdlm.be/javascript/48/partie2/chapitre4/qcm.htm)
Questions ouvertes
(http://odyssey.sdlm.be/javascript/62/partie2/chapitre4/questions.htm)
Le JavaScript est un langage permettant de rendre une page web dynamique du côté du
client. Seulement, quand on pense à « dynamique », on pense aussi à « animations ». Or,
pour faire des animations, il faut savoir accéder au CSS et le modifier. C’est ce que
nous allons étudier dans ce chapitre.
Nous aborderons donc l’édition du CSS et son analyse. Puis, nous verrons comment
réaliser un petit système de drag & drop : un sujet intéressant !
Mais on peut tout aussi bien éditer les feuilles de styles, qui se présentent de la manière
suivante :
div {
color: red; /* Ici on modifie la couleur du texte de tous les éléments <div>*/
}
Rappelons-le : les propriétés CSS de l’attribut style sont prioritaires sur les
propriétés d’une feuille de styles ! Ainsi, dans le code suivant, le texte n’est pas rouge
mais bleu :
<style>
div {
color: red;
}
</style>
Pour le moment, nous nous limiterons à ces brefs rappels sur le CSS, car ce sont
principalement les priorités des styles CSS qui vous seront utiles !
Pour modifier les styles CSS de notre propriété, il suffit ensuite d’indiquer leur nom et
de leur attribuer une valeur, width (pour la largeur) par exemple :
element.style.width = '150px';
// On modifie la largeur de notre élément à 150px
La solution est simple : il suffit de supprimer les tirets et chaque mot suivant
normalement un tiret voit sa première lettre devenir une majuscule. Ainsi, notre code
précédent doit s’écrire de la manière suivante pour fonctionner correctement :
element.style.backgroundColor = 'blue';
// Après suppression du tiret et ajout de la majuscule au 2e mot, le code
fonctionne !
Comme vous pouvez le constater, l’édition du CSS d’un élément n’est pas bien
compliquée. Cependant, il y a une limitation de taille : la lecture des propriétés CSS !
Prenons un exemple :
<style type="text/css">
#myDiv {
background-color: orange;
}
</style>
<script>
var myDiv = document.getElementById('myDiv');
<script>
var myDiv = document.getElementById('myDiv');
<span id="text"></span>
<script>
var text = document.getElementById('text'),
color = getComputedStyle(text).color;
alert(color);
</script>
NOM DE CONTIENT…
L’ATTRIBUT
Contient la largeur complète (width + padding + border) de
offsetWidth
l’élément.
Contient la hauteur complète (height + padding + border) de
offsetHeight
l’élément.
Surtout utile pour les éléments en position absolue.
offsetLeft Contient la position de l’élément par rapport au bord gauche de son
élément parent.
Surtout utile pour les éléments en position absolue.
offsetTop Contient la position de l’élément par rapport au bord supérieur de
son élément parent.
Utile uniquement pour un élément en position absolue ou relative.
offsetParent Contient l’objet de l’élément parent par rapport auquel est
positionné l’élément actuel.
Ces propriétés ne s’utilisent pas comme n’importe quel style CSS, tout simplement
parce que ce ne sont pas des styles CSS ! Ce sont juste des propriétés (en lecture seule),
mises à jour dynamiquement et qui concernent certains états physiques d’un élément.
Pour les utiliser, oubliez la propriété style vu qu’il ne s’agit pas de styles CSS. Il suffit
de les lire directement sur l’objet de notre élément HTML :
alert(el.offsetHeight); // On affiche la hauteur complète de notre élément HTML
La propriété offsetParent
La propriété offsetParent contient l’objet de l’élément parent par rapport auquel est
positionné votre élément actuel.
L’explication qui suit fait appel à des connaissances en HTML et en CSS et non pas en
JavaScript ! Toutefois, il est possible que certains d’entre vous ne soient pas familiers
de ce fonctionnement particulier qu’est le positionnement absolu, nous préférons donc
vous le rappeler.
Lorsque vous décidez de placer l’un de vos éléments HTML en positionnement absolu,
il est sorti du positionnement par défaut des éléments HTML et va se placer tout en haut
à gauche de votre page web, au-dessus de tous les autres éléments. Ce principe n’est
applicable que lorsque votre élément n’est pas déjà lui-même placé dans un élément en
positionnement absolu. Si cela arrive, votre élément se positionnera non plus par
rapport au coin supérieur gauche de la page web, mais par rapport au coin supérieur
gauche du précédent élément placé en positionnement absolu, relatif ou fixe.
Revenons maintenant à la propriété offsetParent. Si elle existe, c’est parce que les
propriétés offsetTop et offsetLeft contiennent le positionnement de votre élément
par rapport à son précédent élément parent et non pas par rapport à la page ! Si nous
voulons obtenir son positionnement par rapport à la page, il faudra alors aussi ajouter
les valeurs de positionnement de son (ses) élément(s) parent(s).
Voici le problème mis en pratique, avec sa solution :
1. <style>
2. #parent,
3. #child {
4. position: absolute;
5. top: 50px;
6. left: 100px;
7. }
8.
9. #parent {
10. width: 200px;
11. height: 200px;
12. background-color: blue;
13. }
14.
15. #child {
16. width: 50px;
17. height: 50px;
18. background-color: red;
19. }
20. </style>
21.
22. <div id="parent">
23. <div id="child"></div>
24. </div>
25.
26. <script>
27. var parent = document.getElementById('parent');
28. var child = document.getElementById('child');
29.
30. alert("Sans la fonction de calcul, la position de l'élément enfant est :
\n\n" +
31. 'offsetTop : ' + child.offsetTop + 'px\n' +
32. 'offsetLeft : ' + child.offsetLeft + 'px');
33.
34.
35. function getOffset(element) { // Notre fonction qui calcule le
// positionnement complet
36. var top = 0,
37. left = 0;
38.
39. do {
40. top += element.offsetTop;
41. left += element.offsetLeft;
42. } while (element = element.offsetParent); // Tant que « element »
// reçoit un « offsetParent » valide alors on additionne les valeurs des offsets
43.
44. return { // On retourne un objet, cela nous permet de retourner
// les deux valeurs calculées
45. top: top,
46. left: left
47. };
48. }
49.
50.
51. alert("Avec la fonction de calcul, la position de l'élément enfant est :
\n\n" +
52. 'offsetTop : ' + getOffset(child).top + 'px\n' +
53. 'offsetLeft : ' + getOffset(child).left + 'px');
54. </script>
• Ensuite, ligne 42, nous attribuons à element l’objet de l’élément parent de #child,
en somme nous montons d’un cran dans l’arbre DOM. L’opération est donc la
suivante :
element = child.offsetParent; // Le nouvel élément est « parent »
• Toujours à la même ligne, element possède une référence vers un objet valide (qui
est l’élément #parent), la condition est donc vérifiée (l’objet est évalué à true) et
la boucle s’exécute de nouveau.
• La boucle se répète en ajoutant cette fois les valeurs de positionnement de l’élément
#parent à nos variables top et left. Le calcul effectué est donc :
• Ligne 42, cette fois l’objet parent de #parent est l’élément <body>. La boucle va
donc se répéter avec <body> qui est un objet valide. Comme nous n’avons pas
modifié ses styles CSS, il ne possède pas de valeurs de positionnement, le calcul
effectué est donc :
top = 300 + 0; // 300
left = 300 + 0; // 300
• Ligne 42, <body> a une propriété offsetParent qui est à undefined, la boucle
s’arrête donc.
Bien que le fonctionnement de cette boucle ne soit pas très complexe, elle peut parfois
être déroutante, il était donc préférable de l’étudier en détail.
Avant de terminer, pourquoi avoir écrit « hauteur complète (width + padding +
border) » dans le tableau précédent ? Il faut savoir qu’en HTML, la largeur (ou
hauteur) complète d’un élément correspond à la valeur de width + celle du padding +
celle des bordures.
Par exemple, sur ce code :
<style>
#offsetTest {
width: 100px;
height: 100px;
padding: 10px;
border: 2px solid black;
}
</style>
<div id="offsetTest"></div>
Présentation de l’exercice
Qu’est-ce que le drag & drop ? Il s’agit d’un système permettant le déplacement
d’éléments par un simple déplacement de souris. Pour faire simple, c’est comme
lorsque vous avez un fichier dans un dossier et que vous le déplacez dans un autre
dossier en le faisant glisser avec votre souris.
Si jusqu’à présent vous avez suivi attentivement le cours et que vous êtes prêts à vous
démener un peu, vous serez tout à fait capables de réaliser un tel système.
Avant de se lancer dans le code, listons les étapes de fonctionnement d’un système de
drag & drop.
1. L’utilisateur clique sur un élément avec le bouton gauche de sa souris (sans relâcher
la pression). Le drag & drop s’initialise alors en sachant qu’il va devoir gérer le
déplacement de cet élément. Pour information, l’événement à utiliser ici est
mousedown.
2. Tout en maintenant le bouton de la souris enfoncé, l’utilisateur déplace son curseur.
L’élément ciblé suit alors ses mouvements à la trace. L’événement à utiliser est
mousemove et nous vous conseillons de l’appliquer à l’élément document (nous
verrons pourquoi dans le corrigé).
3. L’utilisateur relâche le bouton de la souris. Le drag & drop prend alors fin et
l’élément ne suit plus le curseur de la souris. L’événement utilisé est mouseup.
Voici le code HTML de base ainsi que le CSS afin de vous simplifier les choses :
<div class="draggableBox">1</div>
<div class="draggableBox">2</div>
<div class="draggableBox">3</div>
.draggableBox {
position: absolute;
width: 80px; height: 60px;
padding-top: 10px;
text-align: center;
font-size: 40px;
background-color: #222;
color: #CCC;
cursor: move;
}
Corrigé
Vous avez terminé l’exercice ? Nous espérons que vous l’avez réussi ? Si ce n’est pas le
cas, ne vous en faites pas. Étudiez attentivement le corrigé et tout devrait être plus clair.
(function() { // On utilise une IIFE pour ne pas polluer l'espace global
var storage = {}; // Contient l'objet de la div en cours de déplacement
elements[i].addEventListener('mouseup', function() {
// Termine le drag & drop
storage = {};
});
}
document.addEventListener('mousemove', function(e) {
// Permet le suivi du drag & drop
var target = storage.target;
if (target) {
target.style.top = e.clientY - storage.offsetY + 'px';
target.style.left = e.clientX - storage.offsetX + 'px';
}
});
}
Nous avons ici volontairement caché les codes d’ajout d’événements, car c’est cette
boucle qui nous intéresse. Couplée à la méthode querySelectorAll() (voir le
chapitre 10 consacré à la manipulation du code HTML), elle permet de parcourir tous
les éléments HTML filtrés par un sélecteur CSS. Dans notre cas, nous parcourons tous
les éléments avec la classe .draggableBox.
L’ajout des événements mousedown et mouseup
Dans notre boucle qui parcourt le code HTML, nous avons deux ajouts d’événements
que voici :
elements[i].addEventListener('mousedown', function(e) {
// Initialise le drag & drop
var s = storage;
s.target = e.target;
s.offsetX = e.clientX - s.target.offsetLeft;
s.offsetY = e.clientY - s.target.offsetTop;
});
Comme vous le voyez, ces deux événements ne font qu’accéder à la variable storage,
qui est un objet servant d’espace de stockage. Il permet de mémoriser l’élément
actuellement en cours de déplacement ainsi que la position du curseur par rapport à
notre élément (nous reviendrons sur ce dernier point plus tard).
Ainsi, dans notre événement mousedown (qui initialise le drag & drop), nous ajoutons
l’événement ciblé dans la propriété storage.target, puis les positions du curseur par
rapport à notre élément dans storage.offsetX et storage.offsetY.
En ce qui concerne notre événement mouseup (qui termine le drag & drop), nous
attribuons un objet vide à notre variable storage, comme ça tout est vidé !
if (target) {
target.style.top = e.clientY - storage.offsetY + 'px';
target.style.left = e.clientX - storage.offsetX + 'px';
}
});
La valeur X désigne le décalage (en pixels) entre les bordures gauche de l’élément
et du curseur, la valeur Y fait de même entre les bordures supérieures.
Si vous ne le faites pas, à chaque déplacement de votre élément, celui-ci placera son
bord supérieur gauche sous votre curseur, ce qui ne correspond pas du tout à ce que
nous souhaitons.
En résumé
• Pour modifier les styles CSS d’un élément, il suffit d’utiliser la propriété style.
Ensuite, il ne reste plus qu’à accéder à la bonne propriété CSS, par exemple :
element.style.height = '300px'.
• Le nom des propriétés composées doit s’écrire sans tiret et avec une majuscule au
début de chaque mot, à l’exception du premier. Ainsi, border-radius devient
borderRadius.
• La fonction getComputedStyle() récupère la valeur de n’importe quelle propriété
CSS. C’est utile, car la propriété style n’accède pas aux propriétés définies dans la
feuille de styles.
• Les propriétés de type offset, au nombre de cinq, permettent de récupérer des
valeurs liées à la taille et au positionnement.
• Le positionnement absolu peut poser des problèmes. Voilà pourquoi il faut savoir
utiliser la propriété offsetParent, combinée aux autres propriétés offset.
15 Déboguer le code
Dans le premier chapitre traitant du débogage (8), nous avons étudié des exemples
relativement simples, uniquement en rapport avec le JavaScript. Maintenant que nous
avons introduit la manipulation du DOM et du CSS, de nouveaux problèmes peuvent
surgir. Heureusement, il existe des outils permettant d’analyser les erreurs rencontrées
sur ce type de modification, comme nous allons découvrir dans ce chapitre.
L’usage de l’inspecteur web, que nous allons étudier ci-après, n’a que peu de
rapport avec le JavaScript. Il vous permettra cependant d’éviter de nombreux
bogues liés à la modification du DOM.
L’inspecteur web
Un des éléments les plus importants d’un kit de développement est probablement son
inspecteur web. C’est lui qui permet d’étudier la structure du code HTML ainsi que le
CSS associé à chaque élément.
Vous avez peut-être déjà remarqué que lorsque vous affichez le code source d’une page
web en effectuant un clic droit dessus et en sélectionnant Afficher le code source de la
page, le code affiché peut ne pas être à jour par rapport aux modifications apportées par
le JavaScript sur le DOM. En effet, cette manipulation n’affiche que le code source
récupéré par le navigateur web au chargement de la page, il ne tient pas compte des
modifications ultérieures. L’inspecteur web va alors vous être utile.
Pour afficher ce dernier, ouvrez le kit de développement et sélectionnez l’onglet
Elements pour Chrome, Inspecteur pour Firefox. Vous obtenez alors ceci :
L’inspecteur web de Chrome
Vous pouvez maintenant visualiser votre code HTML sous forme d’arbre et vous pouvez
en modifier le contenu en double-cliquant sur un élément, un attribut, une propriété CSS,
etc. Il est aussi possible d’ajouter de nouveaux éléments ou d’en supprimer (clic droit
sur l’élément puis Supprimer le nœud/Delete node), sans passer par le JavaScript, ce
qui vous fera gagner du temps lors de vos phases de débogage !
Avant d’aller plus loin, sachez qu’il existe un raccourci extrêmement pratique pour
afficher l’inspecteur web avec l’élément de notre choix directement sélectionné.
Appuyez sur les touches Ctrl+Maj+C (ou Cmd+Alt+C sous Mac OS X) et cliquez sur
l’élément de votre choix dans la page.
Sélection interactive d’un élément sous Chrome
div {
color: red;
}
La balise <div> hérite de la couleur bleu appliquée au <body> mais la couleur rouge est
appliquée et vient donc réécrire la propriété color.
Si vous cherchez à connaître la valeur finale d’une propriété CSS sur un élément, il peut
parfois être difficile de parcourir la liste de toutes les propriétés CSS appliquées à
l’élément en question car la liste peut très vite s’allonger avec une feuille de styles
relativement importante. Pour cela, dans le panneau des règles CSS, ouvrez l’onglet
Computed (Calculé sous Firefox) afin d’afficher les propriétés CSS finales appliquées
à l’élément HTML sélectionné :
Dans notre exemple, l’utilisation d’un point d’arrêt sur un élément HTML est assez peu
utile. Cependant, sur un site contenant des centaines de lignes de code, cela peut-être
utile pour savoir quelle ligne de code JavaScript vient de modifier un élément HTML.
Dans le menu Break on, vous aviez le choix entre trois options (cumulables).
• Subtree modifications : se déclenche lorsque les nœuds enfants de l’élément ciblé
sont modifiés.
• Attributes modifications : se déclenche pour toute modification apportée aux
attributs de l’élément ciblé.
• Node removal : se déclenche lorsque l’élément ciblé est supprimé.
Pour finir, la suppression d’un point d’arrêt sur un élément HTML est toujours un peu
difficile avec Chrome. Normalement, il suffit de décocher l’option voulue dans le menu
Break on, mais cela ne fonctionne pas toujours. Pour pallier ce problème, vous pouvez
fermer l’onglet, puis en ouvrir un nouveau, les points d’arrêt auront été supprimés.
En résumé
• L’inspecteur web est extrêmement utile pour parcourir du code HTML généré après
le chargement de la page. Il permet aussi de le modifier à volonté.
• Il est possible de déboguer plus facilement vos feuilles de styles grâce au panneau
situé à droite dans l’inspecteur web.
• Les points d’arrêt ne sont pas limités au JavaScript mais peuvent aussi être appliqués
au code HTML.
16 TP : un formulaire interactif
À ce stade, il se peut que vous n’ayez pas encore tout assimilé. Grâce à ce TP, vous
pourrez réviser les notions essentielles abordées dans les chapitres de cette deuxième
partie de l’ouvrage.
Il s’agit ici de créer un formulaire dynamique, c’est-à-dire un formulaire dont une partie
des vérifications est effectuée par le JavaScript, côté client. On peut par exemple
vérifier que l’utilisateur a bien complété tous les champs, ou bien qu’ils contiennent des
valeurs valides (par exemple que le champ Âge ne contient pas de lettres).
Présentation de l’exercice
Qu’allons-nous demander à l’utilisateur via notre formulaire ? Dans notre cas, nous
allons faire simple et classique : un formulaire d’inscription. Nous demanderons donc
quelques informations à l’utilisateur, ce qui nous permettra d’utiliser un peu tous les
éléments HTML spécifiques aux formulaires que nous avons vus jusqu’à présent. Voici
les informations à récupérer ainsi que les types d’éléments HTML :
Bien sûr, chacune de ces informations devra être traitée pour savoir si le contenu est
correct, par exemple, si l’utilisateur a bien spécifié son sexe ou s’il n’a pas saisi de
chiffres dans son prénom, etc. Les vérifications de contenu ne seront pas ici très
poussées car nous n’avons pas encore étudié les « regex »
(http://fr.wikipedia.org/wiki/Expression_rationnelle). Nous nous limiterons donc à la
vérification de la longueur de la chaîne ou à la présence de certains caractères. Rien de
très complexe, mais cela suffira amplement car le but de ce TP n’est pas vraiment de
vous faire analyser le contenu mais plutôt de gérer les événements et le CSS de votre
formulaire.
Voici donc les conditions à respecter pour chaque information :
L’utilisateur n’est pas censé connaître toutes ces conditions quand il arrive sur votre
formulaire, il faudra donc les lui indiquer avant même qu’il ne commence à entrer ses
informations. Pour cela, il va vous falloir afficher la condition d’un champ de texte à
chaque fois que l’utilisateur fera une erreur. Nous parlons ici uniquement des champs de
texte, parce que nous n’allons pas dire à l’utilisateur « Sélectionnez votre sexe » alors
qu’il n’a qu’un bouton radio, cela paraît évident.
Il faudra aussi faire une vérification complète du formulaire lorsque l’utilisateur
cliquera sur le bouton de soumission. S’il n’a pas coché de case pour son sexe, par
exemple, un message d’erreur apparaîtra lui indiquant qu’il manque une information, de
même s’il n’a pas sélectionné de pays.
Vous disposez à présent de toutes les informations nécessaires pour vous lancer dans ce
TP. Vous pouvez concevoir votre propre code HTML ou utiliser celui proposé dans le
corrigé.
Corrigé
Vous avez probablement terminé ou alors vous n’avez pas réussi à aller jusqu’au bout,
ce qui peut arriver !
<head>
<meta charset="utf-8" />
<title>TP : Un formulaire interactif</title>
</head>
<body>
<form id="myForm">
<span class="form_col"></span>
<label><input name="news" type="checkbox" /> Je désire recevoir la newsletter
chaque mois.</label>
<br /><br />
<span class="form_col"></span>
<input type="submit" value="M'inscrire" /> <input type="reset"
value="Réinitialiser le formulaire" />
</form>
</body>
</html>
Vous remarquerez que de nombreuses balises <span> possèdent une classe nommée
.tooltip. Elles contiennent le texte à afficher lorsque le contenu du champ les
concernant ne correspond pas à ce qui est souhaité.
Nous allons maintenant passer au CSS. D’habitude, nous ne vous le fournissons pas
directement, mais cette fois il fait partie intégrante du TP donc le voici :
body {
padding-top: 50px;
}
.form_col {
display: inline-block;
margin-right: 15px;
padding: 3px 0px;
width: 200px;
min-height: 1px;
text-align: right;
}
input {
padding: 2px;
border: 1px solid #CCC;
border-radius: 2px;
outline: none; /* Retire les bordures appliquées par certains navigateurs (Chrome
notamment) lors du focus des éléments <input> */
}
input:focus {
border-color: rgba(82, 168, 236, 0.75);
box-shadow: 0 0 8px rgba(82, 168, 236, 0.5);
}
.correct {
border-color: rgba(68, 191, 68, 0.75);
}
.correct:focus {
border-color: rgba(68, 191, 68, 0.75);
box-shadow: 0 0 8px rgba(68, 191, 68, 0.5);
}
.incorrect {
border-color: rgba(191, 68, 68, 0.75);
}
.incorrect:focus {
border-color: rgba(191, 68, 68, 0.75);
box-shadow: 0 0 8px rgba(191, 68, 68, 0.5);
}
.tooltip {
display: inline-block;
margin-left: 20px;
padding: 2px 4px;
border: 1px solid #555;
background-color: #CCC;
border-radius: 4px;
}
Notez bien les deux classes .correct et .incorrect : elles seront appliquées aux
<input> de type text et password afin de bien montrer si un champ est correctement
rempli ou non.
Nous pouvons maintenant passer au plus compliqué, le code JavaScript :
// Fonction de désactivation de l'affichage des "tooltips"
function deactivateTooltips() {
function getTooltip(elements) {
return false;
var check = {}; // On met toutes nos fonctions dans un objet littéral
check['sex'] = function() {
if (sex[0].checked || sex[1].checked) {
tooltipStyle.display = 'none';
return true;
} else {
tooltipStyle.display = 'inline-block';
return false;
}
};
check['lastName'] = function(id) {
if (name.value.length >= 2) {
name.className = 'correct';
tooltipStyle.display = 'none';
return true;
} else {
name.className = 'incorrect';
tooltipStyle.display = 'inline-block';
return false;
}
};
check['firstName'] = check['lastName'];
// La fonction pour le prénom est la même que celle du nom
check['age'] = function() {
};
check['login'] = function() {
if (login.value.length >= 4) {
login.className = 'correct';
tooltipStyle.display = 'none';
return true;
} else {
login.className = 'incorrect';
tooltipStyle.display = 'inline-block';
return false;
}
};
check['pwd1'] = function() {
if (pwd1.value.length >= 6) {
pwd1.className = 'correct';
tooltipStyle.display = 'none';
return true;
} else {
pwd1.className = 'incorrect';
tooltipStyle.display = 'inline-block';
return false;
}
};
check['pwd2'] = function() {
pwd2.className = 'incorrect';
tooltipStyle.display = 'inline-block';
return false;
}
};
check['country'] = function() {
if (country.options[country.selectedIndex].value != 'none') {
tooltipStyle.display = 'none';
return true;
} else {
tooltipStyle.display = 'inline-block';
return false;
}
};
myForm.addEventListener('submit', function(e) {
if (result) {
alert('Le formulaire est bien rempli.');
}
e.preventDefault();
});
myForm.addEventListener('reset', function() {
deactivateTooltips();
});
})();
deactivateTooltips();
Explications
Les explications vont essentiellement porter sur le code JavaScript qui est plutôt long
(plus de deux cents lignes de code).
Le but ici est donc de cacher tous les éléments qui ont une classe .tooltip.
Récupérer l’infobulle correspondant à un <input>
Il est facile de parcourir toutes les infobulles, mais il est un peu plus délicat de
récupérer celle correspondant à un <input> que nous sommes actuellement en train de
traiter. Si nous regardons bien la structure de notre document HTML, nous constatons
que les infobulles sont toujours placées après l’élément <input> auquel elles
correspondent. Nous allons donc partir du principe qu’il suffit de chercher l’infobulle la
plus « proche » après l’élément <input> que nous sommes actuellement en train de
traiter. Voici le code :
function getTooltip(element) {
return false;
Au premier abord, nous pourrions penser que cette ligne ne sert pas à grand-chose, mais
en réalité elle est d’une très grande utilité : l’objet créé va nous permettre d’y stocker
toutes les fonctions permettant de vérifier (check en anglais, d’où le nom de l’objet)
chaque valeur entrée par l’utilisateur. L’intérêt de cet objet est triple.
• Nous allons pouvoir exécuter la fonction correspondant à un champ de cette
manière : check['id_du_champ']();. Cela va grandement simplifier notre code
lors de la mise en place des événements.
• Il sera possible d’exécuter toutes les fonctions de vérification juste en parcourant
l’objet. Ceci s’avérera très pratique lorsque l’utilisateur cliquera sur le bouton
d’inscription et qu’il faudra alors revérifier tout le formulaire.
• L’ajout d’un champ de texte et de sa fonction d’analyse devient très simple si on
concentre tout dans cet objet. Vous comprendrez très rapidement pourquoi !
Nous n’allons pas étudier toutes les fonctions d’analyse car elles se ressemblent
beaucoup. Nous allons uniquement nous intéresser à deux fonctions :
check['login'] = function() {
if (login.value.length >= 4) {
login.className = 'correct';
tooltipStyle.display = 'none';
return true;
} else {
login.className = 'incorrect';
tooltipStyle.display = 'inline-block';
return false;
}
};
Il est très important que vous constatiez que notre fonction est contenue dans l’index
login de l’objet check. L’index n’est rien d’autre que l’identifiant du champ de texte
auquel la fonction appartient. Le code n’est pas très compliqué : nous récupérons
l’élément <input> et la propriété style de l’infobulle qui correspondent à notre
fonction et nous passons à l’analyse du contenu.
• Si le contenu remplit bien la condition, nous attribuons à notre <input> la classe
.correct, nous désactivons l’affichage de l’infobulle et nous retournons true.
• Si le contenu ne remplit pas la condition, notre <input> se voit alors attribuer la
classe .incorrect et l’infobulle est affichée. En plus de cela, nous renvoyons la
valeur false.
Passons maintenant à la seconde fonction :
check['lastName'] = function(id) {
if (name.value.length >= 2) {
name.className = 'correct';
tooltipStyle.display = 'none';
return true;
} else
name.className = 'incorrect';
tooltipStyle.display = 'inline-block';
return false;
}
};
Cette fonction diffère de la précédente sur un seul point : elle possède un argument id
qui permet de récupérer l’identifiant de l’élément <input> à analyser. Cette fonction va
nous servir à analyser deux champs de texte différents : celui du nom et celui du prénom.
Puisqu’ils ont tous les deux la même condition, il aurait été stupide de créer deux fois la
même fonction.
Ainsi, au lieu de faire appel à cette fonction sans aucun argument, il faut lui passer
l’identifiant du champ de texte à analyser. Nous avons alors deux possibilités :
check['lastName']('lastName');
check['lastName']('firstName');
Cependant, ce fonctionnement pose un problème, car nous étions partis du principe que
nous allions faire appel à nos fonctions d’analyse selon le principe suivant :
check['id_du_champ']();
Or, si nous faisons ça, cela veut dire que nous ferons aussi appel à la fonction
check['firstName']() qui n’existe pas… Nous n’allons pas la créer, sinon nous
perdrons l’intérêt de notre système d’argument sur la fonction check['lastName']().
La solution est donc de faire une référence de la manière suivante :
check['firstName'] = check['lastName'];
Dans la mesure où nous l’avons déjà expliqué précédemment, nous ne reviendrons pas
sur le fonctionnement de cette boucle et de la condition qu’elle contient. Il s’agit ici de
parcourir les <input> de type text ou password.
Penchons-nous en revanche sur les lignes 5 à 7. Elles permettent d’assigner une fonction
anonyme à l’événement keyup de l’élément <input> actuellement traité. La ligne 6 fait
appel à la fonction d’analyse qui correspond à l’élément <input> qui a exécuté
l’événement. Ainsi, si l’élément <input>#login déclenche son événement, il appellera
alors la fonction check['login']().
Cependant, un argument est passé à chaque fonction d’analyse que nous exécutons. Il
s’agit de l’argument nécessaire à la fonction check['lastName'](). Ainsi, lorsque les
éléments <input>#lastName et #firstName déclencheront leur événement, ils
exécuteront alors respectivement les lignes de code suivantes :
check['lastName']('lastName');
et
check['firstName']('firstName');
On passe ici l’argument à toutes les fonctions d’analyse. Normalement, il n’y a pas de
raison pour que cela pose problème. Imaginons que l’élément <input>#login
déclenche son événement, il exécutera alors la ligne de code suivante :
check['login']('login');
Cela fera passer un argument inutile dont la fonction ne tiendra pas compte, c’est tout.
myForm.addEventListener('submit', function(e) {
if (result) {
alert('Le formulaire est bien rempli.');
}
e.preventDefault();
});
myForm.addEventListener('reset', function() {
deactivateTooltips();
});
})();
Comme vous pouvez le constater, nous n’avons pas appliqué d’événements click sur
les boutons, mais nous avons directement appliqué submit et reset sur le formulaire,
ce qui est bien plus pratique dans notre cas.
Notre événement submit va parcourir notre tableau check et exécuter toutes les
fonctions qu’il contient (y compris celles qui ne sont pas associées à un champ de texte
comme check['sex']() et check['country']()). Chaque valeur retournée par ces
fonctions est « ajoutée » à la variable result, ce qui fait que si une des fonctions a
renvoyé false, alors result sera aussi à false et l’exécution de la fonction alert()
ne se fera pas.
Concernant notre événement reset, c’est très simple : nous parcourons les champs de
texte, nous retirons leur classe et ensuite nous désactivons toutes les infobulles grâce à
notre fonction deactivateTooltips().
Troisième partie
Les objets et les design patterns
Nous allons ici étudier la création et la modification des objets. De nouvelles notions
vont être abordées et nous allons approfondir les connaissances sur les objets natifs du
JavaScript.
17 Les objets
Ce chapitre est la suite du chapitre 7 traitant des objets et des tableaux. Il permet
d’aborder l’univers de la création et de la modification d’objets en JavaScript.
Vous découvrirez comment créer un objet en lui définissant un constructeur, des
propriétés et des méthodes. Vous verrez aussi comment modifier un objet natif.
S’ensuivra alors une manière d’exploiter les objets pour les utiliser en tant que
namespaces et nous étudierons la modification du contexte d’exécution d’une méthode.
Pour terminer, la notion d’héritage sera abordée.
Petite problématique
Le JavaScript possède des objets natifs, comme String, Boolean et Array, mais il
permet aussi de créer nos propres objets, avec leurs propres méthodes et propriétés.
L’intérêt est généralement une propreté de code ainsi qu’une facilité de développement.
Les objets sont là pour nous faciliter la vie, mais leur création peut prendre du temps.
Rappelez-vous l’exemple des tableaux avec les prénoms :
var myArray = ['Sébastien', 'Laurence', 'Ludovic', 'Pauline', 'Guillaume'];
Ce tableau sert juste à stocker les prénoms, rien de plus. Imaginons qu’il faille faire un
tableau contenant énormément de données, et ce pour chaque personne. Par exemple,
pour chaque personne, on aurait les données suivantes : prénom, âge, sexe, parenté,
travail… Comment structurer tout cela ?
Avec une très grosse dose de motivation, il est possible de réaliser quelque chose
comme ceci :
var myArray = [
{
nick: 'Sébastien',
age: 23,
sex: 'm',
parent: 'aîné',
work: 'JavaScripteur'
},
{
nick: 'Laurence',
age: 19,
sex: 'f',
parent: 'soeur',
work: 'Sous-officier'
},
// et ainsi de suite…
];
Ce n’est pas encore trop compliqué car les données restent relativement simples.
Maintenant, pour chaque personne, nous allons ajouter un tableau qui contiendra ses
amis, et pour chaque ami, les mêmes données. Cela se complique… Nous allons profiter
de cette problématique pour étudier les objets.
Objet constructeur
Nous avons vu que le JavaScript nous permettait de créer des objets littéraux. Nous
allons voir maintenant comment créer de véritables objets qui possèdent des propriétés
et des méthodes tout comme les objets natifs.
Un objet représente quelque chose, une idée ou un concept. Ici, suite à l’exemple de la
famille, nous allons créer un objet appelé Person qui contiendra des données : le
prénom, l’âge, le sexe, le lien de parenté, le travail et la liste des amis (qui sera un
tableau).
L’utilisation de tels objets se fait en deux temps.
• Nous définissons l’objet via un constructeur, qui pourra être réutilisé par la suite. Cet
objet ne sera pas directement utilisé car nous nous servirons d’une « copie » : on
parle alors d’« instance ».
• À chaque fois que nous avons besoin d’utiliser notre objet, nous créons une instance
de celui-ci, nous le copions.
Utiliser l’objet
L’objet Person a été défini grâce au constructeur qu’il ne nous reste plus qu’à utiliser :
// Définition de l'objet Person via un constructeur
function Person(nick, age, sex, parent, work, friends) {
this.nick = nick;
this.age = age;
this.sex = sex;
this.parent = parent;
this.work = work;
this.friends = friends;
}
// On crée des variables qui vont contenir une instance de l'objet Person :
var seb = new Person('Sébastien', 23, 'm', 'aîné', 'JavaScripteur', []);
var lau = new Person('Laurence', 19, 'f', 'soeur', 'Sous-officier', []);
Que s’est-il passé ici ? L’objet Person a été défini comme nous l’avons vu
précédemment. Pour pouvoir utiliser cet objet, nous définissons une variable qui va
contenir une instance de l’objet Person, c’est-à-dire une copie. Pour indiquer au
JavaScript qu’il faut utiliser une instance, nous utilisons le mot-clé new.
Le mot-clé new ne signifie pas « créer un nouvel objet », mais « créer une nouvelle
instance de l’objet », ce qui est très différent puisque dans le second cas nous créons
une instance, une copie, de l’objet initial, ce qui nous permet de conserver l’objet en
question.
Il est possible de faire un test pour savoir si la variable seb est une instance de
Person. Pour cela, il convient d’utiliser le mot-clé instanceof, comme ceci :
Dans les paramètres de l’objet, nous transmettons les différentes informations pour la
personne. Ainsi, en transmettant 'Sébastien' comme premier paramètre, celui-ci ira
s’enregistrer dans la propriété this.nick, et il sera possible de le récupérer en faisant
seb.nick.
Il sera ainsi possible d’accéder aux différents membres de la famille de cette manière
pour récupérer le travail : myArray[i].work.
Le code de cette méthode est simple : il ajoute un objet Person dans le tableau des
amis.
Aurait-il été possible d’utiliser new this(/* ... */) à la place de new Person(/*
... */) ? Non, car comme nous l’avons vu précédemment, this fait référence à l’objet
dans lequel il est appelé, c’est-à-dire le constructeur Person. Si nous avions fait new
this(/* ... */), cela aurait été équivalent à insérer le constructeur dans lui-même.
Vous auriez donc obtenu une erreur du type « this n’est pas un constructeur ».
Ajouter une méthode via prototype
Lorsque vous définissez un objet, il possède automatiquement un sous-objet appelé
prototype.
prototype est un objet, mais c’est aussi le nom d’une bibliothèque JavaScript
(http://www.prototypejs.org/).
Cet objet prototype va nous permettre d’ajouter des méthodes à un objet. Voici
comment ajouter une méthode addFriend() à notre objet Person :
Person.prototype.addFriend = function(nick, age, sex, parent, work, friends) {
this.friends.push(new Person(nick, age, sex, parent, work, friends));
}
Le this fait ici aussi référence à l’objet dans lequel il s’exécute, c’est-à-dire l’objet
Person.
En réalité, l’ajout de méthodes par prototype est particulier, car les méthodes
ainsi ajoutées ne seront pas copiées dans les instances de votre objet.
Autrement dit, en ajoutant la méthode addFriend() par prototype, une
instance comme seb ne possédera pas la méthode directement dans son
propre objet, elle sera obligée d’aller la chercher au sein de son objet
constructeur, ici Person. Cela veut dire que si vous faites une modification sur
une méthode contenue dans un prototype, alors vous affecterez toutes les
instances de votre objet (y compris celles qui sont déjà créées). Cette solution
est donc à privilégier.
Comme il s’agit d’un objet, le type natif est Object. Comme vu précédemment, nous
allons utiliser son sous-objet prototype pour lui ajouter la méthode voulue :
// Testons si cette méthode n'existe pas déjà !
if (!Object.prototype.debug) {
// Créons la méthode
Object.prototype.debug = function() {
var text = 'Object {\n';
alert(text + '}');
}
Mais pourquoi tester si i est différent de 'debug' ? Parce qu’en ajoutant la méthode
debug() aux objets, elle s’ajoute même aux objets littéraux : autrement dit, debug() va
se lister elle-même, ce qui n’a pas beaucoup d’intérêt. Regardez donc le résultat en
enlevant cette condition :
Le code de la méthode debug() est affiché.
Nous avons ajouté une méthode à Object. Nous l’avons fait pour l’exemple, mais ceci
ne doit jamais être reproduit dans vos scripts pour une raison très simple : après ajout
d’une méthode ou d’une propriété à Object, celle-ci sera listée à chaque fois que vous
utiliserez un for in. Par exemple, le code suivant ne devrait même pas afficher une
seule alerte :
var myObject = {};
Et pourtant, après l’ajout d’une méthode comme debug(), votre boucle affichera cette
méthode pour tout objet parcouru, ce qui n’est clairement pas conseillé. Cependant,
notez bien que cette restriction s’applique uniquement à l’objet natif Object, les autres
objets comme Array, String, etc., ne sont pas concernés.
Les namespaces
En informatique, un namespace (« espace de nom » en français) est un ensemble fictif
qui contient des informations, généralement des propriétés et des méthodes, ainsi que
des sous-namespaces. Le but d’un namespace est de s’assurer de l’unicité des
informations qu’il contient.
Par exemple, Sébastien et Johann habitent tous deux au numéro 42. Sébastien dans la rue
de Belgique et Johann dans la rue de France. Les numéros de leur maison peuvent être
confondus, puisqu’il s’agit du même. Ainsi, si Johann dit « J’habite au numéro 42 »,
c’est ambigu, car Sébastien aussi. Alors, pour différencier les deux numéros, nous
allons toujours donner le nom de la rue. Ce nom de rue peut être vu comme un
namespace : il permet de différencier deux données identiques.
En programmation, quelque chose d’analogue peut se produire : imaginez que vous
développiez une fonction myBestFunction(), et vous trouvez un script tout fait pour
réaliser un effet quelconque. Vous ajoutez alors ce script au vôtre. Mais il y a un
problème cat il contient également une fonction myBestFunction()… Votre fonction se
retrouve écrasée par l’autre, et votre script ne fonctionnera plus correctement.
Pour éviter ce genre de désagrément, nous allons utiliser un namespace.
Définir un namespace
Un namespace est une sorte de catégorie : vous allez créer un namespace et vous allez
placer vos fonctions au sein de celui-ci. Ainsi vos fonctions seront en quelque sorte
préservées d’éventuels écrasements. Comme le JavaScript ne gère pas nativement les
namespaces, c’est-à-dire que nous ne disposons pas de structure consacrée à cela, nous
allons devoir nous débrouiller seuls, et utiliser un simple objet littéral.
Premier exemple :
var myNamespace = {
myBestFunction: function() {
alert('Ma meilleure fonction !');
}
};
// On exécute la fonction :
myNamespace.myBestFunction();
Nous commençons par créer un objet littéral appelé myNamespace. Ensuite, nous
définissons une méthode : myBestFunction(). Souvenez-vous, dans le chapitre sur les
objets littéraux, nous avions vu que nous pouvions définir des propriétés, et il est aussi
possible de définir des méthodes, en utilisant la même syntaxe.
Pour appeler la méthode myBestFunction(), il faut obligatoirement passer par l’objet
myNamespace, ce qui limite très fortement la probabilité de voir votre fonction écrasée
par une autre. Bien évidemment, votre namespace doit être original pour être certain
qu’un autre développeur n’utilise pas le même… Cette technique n’est donc pas
infaillible, mais réduit considérablement les problèmes.
Un style de code
Utiliser un namespace est aussi élégant, car cela permet d’avoir un code visuellement
propre et structuré. Une grande majorité des « gros » scripts sont organisés via un
namespace, notamment car il est possible de décomposer le script en catégories. Par
exemple, vous faites un script qui gère un webmail (comme Outlook Mail ou GMail) :
var thundersebWebMail = {
// Propriétés
version: 1.42,
lang: 'english',
// Initialisation
init: function() { /* initialisation */ },
C’est tout de suite plus confus, il n’y a pas de hiérarchie, c’est brouillon. Bien
évidemment, cela dépend du codeur : un code en namespace peut être brouillon, alors
qu’un code « normal » peut être très propre ; mais de manière générale, un code en
namespace est accessible, plus lisible et plus compréhensible. C’est évidemment une
question d’habitude.
L’emploi de this
Le mot-clé this s’utilise ici exactement comme dans les objets vus précédemment.
Mais attention, si vous utilisez this dans un sous-domaine de nom, il pointera vers ce
dernier et non vers le namespace parent. Ainsi, l’exemple suivant ne fonctionnera pas
correctement car en appelant la méthode init(), on lui demande d’exécuter
this.test(). Or, this pointe vers subNamespace et il n’existe aucune méthode
test() au sein de subNamespace.
var myNamespace = {
test: function() {
alert('Test');
},
subNamespace: {
init: function() {
this.test();
}
}
};
myNamespace.subNamespace.init();
Pour accéder à l’objet parent, il n’y a malheureusement pas de solution si ce n’est écrire
son nom entièrement :
var myNamespace = {
test: function() {
alert('Test');
},
subNamespace: {
init: function() {
myNamespace.test();
}
}
};
myNamespace.subNamespace.init();
var myNamespace = {
// Tout le code
};
} else {
alert('myNamespace existe déjà !');
}
Comme vous le savez, une méthode utilise généralement le mot-clé this pour savoir à
quel objet elle appartient, c’est ce qui fait qu’elle est dépendante. Les deux méthodes
apply() et call() existent pour permettre de rediriger la référence du mot-clé this
vers un autre objet.
Nous n’allons pas faire de cas pratique avec la méthode push(), car son fonctionnement
est spécifique aux tableaux (il serait difficile de lui demander d’ajouter une donnée sur
un objet dont la structure est totalement différente). En revanche, il existe une méthode
que tout objet possède : toString(). Cette méthode a pour but de fournir une
représentation d’un objet sous forme de chaîne de caractères, c’est elle qui est appelée
par la fonction alert() lorsque vous lui passez un objet en paramètre. Elle possède
cependant un fonctionnement différent selon l’objet sur lequel elle est utilisée :
alert(['test']); // Affiche : « test »
alert({0:'test'}); // Affiche : « [object Object] »
Nous n’avons pas fait appel à la méthode toString(), mais, comme nous
l’avons dit précédemment, alert() le fait implicitement.
Dans notre exemple actuel, la méthode à modifier est toString() de l’objet Object.
Nous écrivons donc ceci :
var result = Object.prototype.toString.call(['test']);
La méthode toString() de Object a bien été appliquée à notre tableau, nous obtenons
donc son type et non pas son contenu.
Revenons maintenant sur les arguments facultatifs de nos deux méthodes apply() et
call(). La première prend en paramètre facultatif un tableau de valeurs, tandis que la
seconde prend une infinité de valeurs en paramètres. Ces arguments facultatifs ont la
même utilité : ils seront passés en paramètres à la méthode souhaitée.
Ainsi, si nous écrivons :
var myArray = [];
myArray.push(1, 2, 3);
myArray.push.call(myArray, 1, 2, 3);
myArray.push(1, 2, 3);
L’héritage
Comme pour beaucoup d’autres langages, il est possible, en JavaScript d’appliquer le
concept d’héritage aux objets. Prenons l’exemple d’une voiture et d’un camion : vous
voulez créer un objet constructeur pour chacun de ces deux véhicules, mais vous vous
rendez compte que vous allez probablement devoir dupliquer votre code car ces deux
véhicules ont tous deux la capacité de rouler, possèdent une plaque d’immatriculation et
un réservoir d’essence. Arrêtons-nous là pour les similitudes, cela sera amplement
suffisant.
Au lieu de dupliquer le code entre les deux véhicules, nous allons faire appel à la notion
d’héritage en créant un objet constructeur parent qui va rassembler ces caractéristiques
communes :
function Vehicle(licensePlate, tankSize) {
this.engineStarted = false; // Notre véhicule a-t-il démarré ?
this.licensePlate = licensePlate; // La plaque d'immatriculation de
// notre véhicule.
this.tankSize = tankSize; // La taille de notre réservoir en litres.
}
Maintenant que notre objet Vehicle est prêt, nous pouvons l’exploiter. L’héritage va ici
consister à créer deux objets constructeurs (un pour notre voiture et un pour notre
camion) qui vont tous deux hériter de Vehicle. Concrètement, cela signifie que nos
deux objets constructeurs vont bénéficier des mêmes propriétés et méthodes que leur
parent et vont pouvoir ajouter à cela leurs propres propriétés et méthodes.
Commençons par la voiture qui va ajouter quelques fonctionnalités concernant son
coffre :
function Car(licensePlate, tankSize, trunkSize) {
// On appelle le constructeur de « Vehicle » par le biais de la méthode
// call() afin qu'il affecte de nouvelles propriétés à « Car ».
Vehicle.call(this, licensePlate, tankSize);
Car.prototype.closeTrunk = function() {
this.trunkOpened = false;
};
Il est maintenant possible d’instancier une nouvelle voiture de manière tout à fait
classique :
var myCar = new Car('AA-555-AA', 70, 2.5);
Cette voiture est maintenant capable de démarrer et d’arrêter son moteur, d’ouvrir et de
fermer son coffre. Nous connaissons également sa plaque d’immatriculation ainsi que la
taille de son réservoir et de son coffre.
this.trailersNumber = trailersNumber;
// Le nombre de remorques attachées à notre camion.
}
Truck.prototype = Object.create(Vehicle.prototype), {
constructor: {
value: Truck,
enumerable: false,
writable: true,
configurable: true
}
});
Truck.prototype.addTrailer = function() {
this.trailersNumber++;
};
Truck.prototype.removeTrailer = function() {
this.trailersNumber--;
};
Nous avons donc créé deux objets constructeurs qui possèdent des caractéristiques
communes regroupées au sein d’un objet constructeur parent. Ainsi, chaque objet enfant
n’a qu’à prendre en charge ses propres particularités, les aspects communs seront gérés
par l’objet parent, ce qui évite de dupliquer le code. Dans le cas où celui-ci serait
relativement complexe, il est bien évidemment possible de faire hériter un objet d’un
parent qui hérite lui-même d’un autre parent et ainsi de suite…
Dans la pratique, il est probable que l’héritage vous soit assez peu utile en JavaScript,
mais pensez-y si votre code devient trop complexe car cela vous permettra de mieux le
segmenter.
18 Les chaînes de caractères
Les chaînes de caractères, représentées par l’objet String, ont déjà été manipulées
précédemment mais il reste bien des choses à étudier à leur propos. Nous allons dans
un premier temps découvrir les types primitifs car cela nous sera nécessaire pour
vraiment parler des objets comme String, RegExp et autres que nous verrons par la
suite.
Cet exemple crée une chaîne de caractères primitive, qui n’est pas un objet String.
Pour instancier un objet String, il faut faire comme ceci :
var myRealString = new String("Chaîne");
Ce que nous avons utilisé jusqu’à présent était en fait des types primitifs, et non des
instances d’objets.
La différence entre les deux est minime pour nous, développeurs. Prenons l’exemple de
la chaîne de caractères : à chaque fois que nous allons faire une opération sur une
chaîne primitive, le JavaScript va automatiquement convertir cette chaîne en une
instance temporaire de String, de manière à pouvoir utiliser les propriétés et méthodes
fournies par l’objet String. Une fois les opérations terminées, l’instance temporaire est
détruite.
Au final, utiliser un type primitif ou une instance revient au même du point de vue de
l’utilisation. Mais il subsiste de légères différences avec l’opérateur instanceof qui
peut retourner de drôles de résultats…
Imaginons que nous voulions savoir de quelle instance est issu un objet :
var myString = 'Chaîne de caractères';
La condition sera fausse ! Et c’est bien normal puisque myString est une chaîne
primitive et non une instance de String. Pour tester le type primitif, il convient
d’utiliser l’opérateur typeof :
if (typeof myString === 'string') {
// Faire quelque chose
}
typeof permet de vérifier le type primitif (en anglais on parle de datatype). Mais ici
aussi, faites attention au piège, car la forme primitive d’une instance de String est…
object :
Il faudra donc faire bien attention en testant le type ou l’instance d’un objet. Il est même
d’ailleurs déconseillé de faire ce genre de test vu le nombre de problèmes que cela peut
causer. La seule valeur retournée par typeof dont nous pouvons être sûrs, c’est
undefined. Nous verrons plus loin dans ce chapitre une solution permettant de tester si
une variable contient une chaîne de caractères.
Retenez bien une chose : il est plus simple en tout point d’utiliser directement
les types primitifs.
L’objet String
Vous manipulez l’objet String depuis le début de cet ouvrage : c’est lui qui gère les
chaînes de caractères.
Propriétés
String ne possède qu’une seule propriété, length, qui retourne le nombre de
caractères contenus dans une chaîne. Les espaces, les signes de ponctuation, les chiffres,
etc., sont considérés comme des caractères. Ainsi, la chaîne de caractères suivante
contient 21 caractères :
alert('Ceci est une chaîne !'.length);
Méthodes
String possède quelques méthodes qui sont pour la plupart assez intéressantes mais
basiques. Le JavaScript est un langage assez simple, qui ne contient pas énormément de
méthodes de base. C’est un peu l’inverse du PHP qui contient une multitude de fonctions
pour réaliser un peu tout et n’importe quoi. Par exemple, le PHP possède une fonction
pour mettre la première lettre d’une chaîne en majuscules, alors qu’en JavaScript il
faudra d’abord récupérer le premier caractère, puis le mettre en majuscules. Le
JavaScript fournit juste quelques méthodes élémentaires, vous devrez coder les autres
méthodes ou fonctions selon vos besoins.
En fait, les chaînes de caractères peuvent être imaginées comme des tableaux, à la
différence qu’il n’est pas possible d’accéder aux caractères en utilisant les crochets.
Pour cela, il faut utiliser charAt().
Quel est l’intérêt d’utiliser les codes ASCII ? Ce n’est pas très fréquent, mais cela peut
être utile dans un cas bien particulier : détecter les touches du clavier. Ainsi, si vous
souhaitez savoir sur quelle touche l’utilisateur a appuyé, vous utiliserez la propriété
keyCode de l’objet Event :
<textarea onkeyup="listenKey(event)"></textarea>
alert('La touche numéro ' + key + ' a été pressée. Le caractère ' +
String.fromCharCode(key) + ' a été inséré.');
}
(Essayez le code complet : http://course.oc-static.com/ftp-
tutos/cours/javascript/part3/chap2/ex3.html)
event.keyCode retourne le code ASCII qui correspond au caractère inséré par la
touche. Pour la touche J, le code ASCII est 74, par exemple.
Cela fonctionne bien pour les touches de caractère, mais pas pour les touches
de fonction comme Delete, Enter, Shift, Caps, etc. Ces touches retournent
bien un code ASCII, mais celui-ci ne peut être converti en un caractère lisible
par un humain. Par exemple, le keyCode de la barre d’espace est 32. Donc
pour savoir si l’utilisateur a pressé ladite barre, il suffira de tester si le keyCode
vaut 32 et d’agir en conséquence.
Le tilde sert très peu en temps normal, mais dans le cadre d’une utilisation avec les deux
méthodes étudiées, il est redoutablement efficace pour détecter si une chaîne de
caractères contient un caractère ou un morceau de chaîne. Habituellement, nous ferions
comme ceci :
1. var myString = 'Le JavaScript est plutôt cool';
2.
3. if (myString.indexOf('JavaScript') != -1) {
4. alert('La chaîne contient bien le mot "JavaScript".');
5. }
if (~myString.indexOf('JavaScript')) {
alert('La chaîne contient bien le mot "JavaScript".');
}
Ainsi, si le résultat vaut -1, il va être incrémenté et arriver à 0, ce qui donnera donc une
évaluation à false pour notre chaîne de caractères. La valeur -1 étant la seule à
pouvoir atteindre la valeur 0 avec le tilde ~, il n’y a pas d’hésitation à avoir vu que tous
les autres nombres seront évalués à true.
Cette technique a été conçue pour les parfaits fainéants, mais il y a fort à parier que
vous vous en souviendrez.
Nous allons ici récupérer dans deux variables différentes, les deux pseudonymes
contenus dans myString. Pour ce faire, nous allons utiliser substring().
substring(a, b) permet d’extraire une chaîne à partir de la position a (incluse)
jusqu’à la position b (exclue).
Pour extraire « Thunderseb », il suffit de connaître la position du premier espace,
puisque la position de départ vaut 0 :
var nick_1 = myString.substring(0, myString.indexOf(' '));
alert(splitted.length); // 3
split() peut aussi couper une chaîne à chaque fois qu’un retour à la ligne est
rencontré : myString.split('\n'). C’est très pratique pour créer un tableau
où chaque item contient une ligne.
l’instruction typeof montre bien que string_1 est une instance de String et que
string_2 est une valeur primitive :
Pour rappel, Object est l’objet dont tous les autres objets (comme String) héritent.
Ainsi, en créant une instance de Object avec un type primitif en paramètre, l’objet
instancié sera de même type que la valeur primitive. En clair, si vous passez en
paramètre un type primitif string, vous obtiendrez une instance de l’objet String avec
la même valeur passée en paramètre.
En résumé
• Il existe des objets et des types primitifs. Si leur utilisation semble identique, il faut
faire attention lors des tests avec les opérateurs instanceof et typeof. Mais
heureusement, valueOf()sera d’une aide précieuse.
• Il est préférable d’utiliser les types primitifs, comme nous le faisons depuis le début
de cet ouvrage.
• String fournit des méthodes pour manipuler les caractères : mettre en majuscules ou
en minuscules, récupérer un caractère grâce à sa position ou encore supprimer les
espaces de part et d’autre de la chaîne.
• String fournit aussi des méthodes pour rechercher, couper et extraire.
• L’utilisation du caractère tilde est méconnue, mais peut se révéler très utile
conjointement à indexOf() ou lastIndexOf().
19 Les expressions régulières :
les bases
Dans ce chapitre, nous allons aborder une notion assez complexe mais très utile : les
expressions régulières. Souvent surnommées « regex », on les trouve dans bon nombre
de langages, comme le Perl, le Python ou encore le PHP.
Les regex sont une sorte de langage à part qui sert à manipuler les chaînes de caractères.
Voici quelques exemples de ce que les regex sont capables de faire.
• Vérifier si une URL entrée par l’utilisateur ressemble effectivement à une URL. Il est
aussi possible de vérifier les adresses électroniques, les numéros de téléphone et
toute autre syntaxe structurée.
• Rechercher et extraire des informations hors d’une chaîne de caractères (bien plus
puissant que d’utiliser indexOf() et substring()).
• Supprimer certains caractères, et au besoin les remplacer par d’autres.
• Pour les forums, convertir des langages comme le BBCode en HTML lors des
prévisualisations en cours de frappe.
• Et bien d’autres choses encore…
Si vous avez déjà appris le PHP, vous avez certainement vu que ce langage
supporte deux types de regex : les regex POSIX et les regex PCRE. Ici,
oubliez les POSIX ! En effet, les regex PCRE sont semblables aux regex Perl
(avec quelques nuances), donc à celles du JavaScript.
Utilisation
Les regex ne s’utilisent pas seules et il y a deux manières de s’en servir : soit par le
biais de RegExp qui est l’objet qui gère les expressions régulières, soit par le biais de
certaines méthodes de l’objet String :
• match() : retourne un tableau contenant toutes les occurrences recherchées ;
• search() : retourne la position d’une portion de texte (semblable à indexOf() mais
avec une regex) ;
• split() : la fameuse méthode split(), mais avec une regex en paramètre ;
• replace() : effectue un rechercher/remplacer.
Avant de mettre en application ces quatre méthodes, nous allons d’abord nous entraîner
à écrire et à tester des regex. Pour ce faire, nous utiliserons la méthode test() fournie
par l’objet RegExp. L’instanciation d’un objet RegExp se fait comme ceci :
var myRegex = /contenu_à_rechercher/;
Cela ressemble à une chaîne de caractères à l’exception près qu’elle est encadrée par
deux slashs / au lieu des apostrophes ou guillemets traditionnels.
Si votre regex contient des slashs, n’oubliez pas de les échapper en utilisant un antislash
comme suit :
var regex_1 = /contenu_/_contenu/;
// La syntaxe est fausse car le slash n'est pas échappé
var regex_2 = /contenu_\/_contenu/;
// La syntaxe est bonne car le slash est échappé avec un antislash
L’utilisation de la méthode test() est très simple. En cas de réussite du test, elle
renvoie true ; dans le cas contraire, elle renvoie false.
if (myRegex.test('Chaîne de caractères dans laquelle effectuer la recherche')) {
// Retourne true si le test est réussi
} else {
// Retourne false dans le cas contraire
}
Pour vos tests, n’hésitez pas à utiliser une syntaxe plus concise, comme ceci :
if (/contenu_à_rechercher/.test('Chaîne de caractères bla bla bla'))
Recherche de mots
Le sujet étant complexe, nous allons commencer par des choses simples, c’est-à-dire
des recherches de mots Ce n’est pas si anodin, car elle permet déjà de faire beaucoup
de choses. Comme nous venons de le voir, une regex s’écrit comme suit :
/contenu_de_la_regex/
Avec cette regex, nous saurons si la phrase contient une de ces quatre spécialités
savoyardes !
Le tableau suivant présente différents tests, réalisés pour montrer l’utilisation de ces
deux symboles :
Une classe de caractères est écrite entre crochets et sa signification est simple : une des
lettres qui se trouve entre les crochets peut convenir. Cela veut donc dire que l’exemple
précédent va trouver les mots « gras » et « gros », car la classe, à la place de la voyelle,
contient aux choix les lettres a et o. Beaucoup de caractères peuvent être utilisés au sein
d’une classe :
/gr[aèio]s/
Ici, la regex trouvera les mots « gras », « grès », « gris » et « gros ». Ainsi, en parlant
d’une tartiflette, qu’elle soit grosse ou grasse, cette regex le saura :
CHAÎNE REGEX RÉSULTAT
/Cette tartiflette est
Cette tartiflette est grosse gr[ao]sse/
true
/Cette tartiflette est
Cette tartiflette est grasse gr[ao]sse/
true
Les quantificateurs
Les quantificateurs permettent de dire combien de fois un caractère doit être recherché.
Il est possible de dire qu’un caractère peut apparaître 0 ou 1 fois, 1 fois ou une infinité
de fois, ou même, avec des accolades, de dire qu’un caractère peut être répété 3, 4, 5 ou
10 fois.
À partir d’ici, la syntaxe des regex va devenir plus complexe !
Ici, le premier t sera trouvé. Un point d’interrogation est placé après le second t, ce qui
signifie qu’il peut apparaître 0 ou 1 fois. Cette regex gère donc notre cas de figure.
Un cas saugrenu serait qu’il y ait beaucoup de t : « raclettttttttttte ». Il suffit alors
d’utiliser le quantificateur + :
/raclet+e/
Le signe + indique que le t sera présent une fois ou un nombre infini de fois. Avec le
signe *, la lettre est facultative mais peut être répétée un nombre infini de fois. En
utilisant le signe *, la regex précédente peut s’écrire :
/raclett*e/
Les accolades
À la place des trois symboles vus précédemment, nous pouvons utiliser des accolades
pour définir explicitement combien de fois un caractère peut être répété. Trois syntaxes
sont disponibles :
• {n} : le caractère est répété n fois ;
• {n,m} : le caractère est répété de n à m fois. Par exemple, si nous avons {0, 5}, le
caractère peut être présent de 0 à 5 fois ;
• {n,} : le caractère est répété de n fois à l’infini.
Si la tartiflette possède un, deux ou trois t, la regex peut s’écrire :
/raclet{1,3}e/
Les quantificateurs, accolades ou symboles, peuvent aussi être utilisés avec les classes
de caractères. Si nous mangeons une « racleffe » au lieu d’une « raclette », il est
possible d’imaginer la regex suivante :
/racle[tf]+e/
Pour clore cette section, voici quelques exemples de regex qui utilisent tout ce qui a été
vu :
La dernière regex est fausse à cause de l’espace. En effet, la classe [a-z] ne trouvera
pas l’espace. Nous verrons cela dans le prochain chapitre.
Les métacaractères
Nous avons vu précédemment que la syntaxe des regex est définie par un certain nombre
de caractères spéciaux, comme ^, $, [ et ), ou encore + et *. Ces caractères sont des
métacaractères, dont voici la liste complète :
! ^ $ ( ) [ ] { } ? + * . / \ |
Un problème se poserait si nous voulions chercher la présence d’une accolade dans une
chaîne de caractères. En effet, si nous avions ceci, la regex ne fonctionnerait pas :
Ceci est tout à fait normal car les accolades sont des métacaractères qui définissent un
nombre de répétitions : en clair, cette regex n’a aucun sens pour l’interpréteur
JavaScript ! Pour pallier ce problème, il suffit d’échapper les accolades avec un
antislash :
/accolade \{comme ceci\}/
De cette manière, les accolades seront vues par l’interpréteur comme étant des
accolades « dans le texte », et non comme des métacaractères. Il en va de même pour
tous les métacaractères cités précédemment. Il faut même penser à échapper l’antislash
avec… un antislash :
Si le logiciel que vous utilisez pour rédiger en JavaScript fait bien son travail, la
première regex provoquera une mise en commentaire (à cause des deux slashs
/ au début) : c’est un bon indicateur pour dire qu’il y a un problème. Si votre
logiciel détecte aussi les erreurs de syntaxe, il peut vous être d’une aide
précieuse.
Une petite exception concernant le tiret : il n’a pas besoin d’être échappé s’il
est placé en début ou en fin de classe.
Et s’il faut trouver un slash ou un antislash, il ne faut pas oublier de les échapper :
/[a-z!?\/\\]/
TYPE DESCRIPTION
\d Trouve un caractère décimal (un chiffre)
\D Trouve un caractère qui n’est pas décimal (donc pas un chiffre)
\s Trouve un caractère blanc
\S Trouve un caractère qui n’est pas un caractère blanc
Trouve un caractère « de mot » : une lettre, accentuée ou non, ainsi
\w
que l’underscore
\W Trouve un caractère qui n’est pas un caractère « de mot »
TYPE DESCRIPTION
\n Trouve un retour à la ligne
\t Trouve une tabulation
Ces deux caractères sont reconnus par le type générique \s (qui trouve n’importe quel
espace blanc).
Les assertions
Les assertions s’écrivent comme les types génériques mais ne fonctionnent pas tout à fait
de la même façon. Un type générique recherche un caractère, tandis qu’une assertion
recherche entre deux caractères. Voici un tableau pour mieux comprendre :
TYPE DESCRIPTION
\b Trouve une limite de mot
\B Ne trouve pas de limite de mot
Au chapitre suivant, nous étudierons l’utilisation des regex avec diverses méthodes
JavaScript.
En résumé
• Les regex constituent une technologie à part, utilisée au sein du JavaScript et qui
permet de manipuler les chaînes de caractères. La syntaxe de ces regex se base sur
celle du langage Perl.
• Plusieurs méthodes de l’objet String peuvent être utilisées avec des regex, à savoir
match(), search(), split() et replace().
• L’option i indique à la regex que la casse doit être ignorée.
• Les caractères ^ et $ indiquent respectivement le début et la fin de la chaîne de
caractères.
• Les classes et les intervalles de caractères, ainsi que les types génériques, servent à
rechercher un caractère possible parmi plusieurs.
• Les différents métacaractères doivent absolument être échappés.
• Les quantificateurs servent à indiquer le nombre de fois qu’un caractère peut être
répété.
20 Les expressions régulières :
les notions avancées
Après avoir vu les bases de la syntaxe des regex au chapitre précédent, nous allons nous
intéresser à l’utilisation des regex au sein du JavaScript. Vous verrez qu’une fois
couplées à un langage de programmation, les regex deviennent très utiles. En JavaScript,
elles utilisent l’objet RegExp et permettent de faire tout ce que nous attendons d’elles :
rechercher un terme, le capturer, le remplacer, etc.
Nous ajoutons ensuite l’arobase qui n’est pas un métacaractère, il est donc inutile de
l’échapper :
/^[a-z0-9._-]+@$/
Puis le label du nom de domaine, lui aussi composé de lettres, de chiffres, de tirets et de
traits de soulignement. Ne pas oublier le point, car il peut s’agir d’un sous-domaine (par
exemple, @cours.eyrolles.com) :
/^[a-z0-9._-]+@[a-z0-9._-]+$/
Pour finir, nous indiquons l’extension, qui ne contient que des lettres (au minimum 2, au
maximum 6). Nous obtenons alors :
/^[a-z0-9._-]+@[a-z0-9._-]+\.[a-z]{2,6}$/
if (/^[a-z0-9._-]+@[a-z0-9._-]+\.[a-z]{2,6}$/.test(email)) {
alert("Adresse e-mail valide !");
} else {
alert("Adresse e-mail invalide !");
}
L’objet RegExp
L’objet RegExp est l’objet qui gère les expressions régulières. Il y a donc deux façons
de déclarer une regex : via RegExp ou via son type primitif que nous avons utilisé
jusqu’à présent :
var myRegex1 = /^Raclette$/i;
var myRegex2 = new RegExp("^Raclette{{CONTENT}}quot;, "i");
Ce n’est pas spécialement fréquent, mais cela peut se révéler particulièrement utile. Il
est cependant conseillé d’utiliser la notation littérale (le type primitif) quand
l’utilisation du constructeur RegExp n’est pas nécessaire.
Méthodes
RegExp ne possède que deux méthodes : test() et exec(). La méthode test() a déjà
été utilisée et permet de tester une expression régulière. Elle renvoie true si le test est
réussi, false si le test échoue. De son côté, exec() applique également une expression
régulière, mais renvoie un tableau dont le premier élément contient la portion de texte
trouvée dans la chaîne de caractères. Si rien n’est trouvé, null est renvoyé.
var sentence = "Si ton tonton";
Propriétés
L’objet RegExp contient neuf propriétés, appelées $1, $2, $3, etc., jusqu’à $9. Comme
nous allons le voir, il est possible d’utiliser une regex pour extraire des portions de
texte, lesquelles sont accessibles via les propriétés $1 à $9.
Les parenthèses
Les parenthèses capturantes
Pour le moment, nous avons vu que les regex permettent de comparer une chaîne de
caractères à un modèle. Mais il est aussi possible d’extraire des informations. Pour
définir les informations à extraire, nous utilisons les « parenthèses capturantes »,
nommées ainsi car elles permettent de capturer une portion de texte, que la regex va
extraire.
Considérons cette chaîne de caractères : « Je suis né en mars ». Au moyen de
parenthèses capturantes, nous allons extraire le mois de la naissance pour pouvoir le
réutiliser :
var birth = 'Je suis né en mars';
Cet exemple est un peu déroutant, mais il est en réalité assez simple à comprendre. Dans
un premier temps, nous créons la regex avec les parenthèses. Comme les mois
comportent des caractères qui peuvent être accentués, nous pouvons directement utiliser
le type générique \S. \S+ indique que nous recherchons une série de caractères, jusqu’à
la fin de la chaîne (délimitée, par $) : ce sera le mois. Nous englobons ce « mois » dans
des parenthèses pour faire comprendre à l’interpréteur JavaScript que leur contenu
devra être extrait.
La regex est exécutée via exec(). Une autre explication s’impose ici. Lorsque nous
exécutons test() ou exec(), le contenu des parenthèses capturantes est enregistré
temporairement au sein de l’objet RegExp. Le premier couple de parenthèses sera
enregistré dans la propriété $1, le deuxième dans $2 et ainsi de suite, jusqu’au neuvième
qui sera dans $9. Cela veut donc dire qu’il ne peut y avoir qu’un maximum de neuf
couples de parenthèses. Les couples sont numérotés suivant le sens de lecture, de
gauche à droite.
Pour accéder aux propriétés, il suffit de faire RegExp.$1, RegExp.$2, etc.
Voici un autre exemple, reprenant la regex de validation de l’adresse e-mail. Ici, le but
est de décomposer l’adresse pour récupérer les différentes parties :
var email = prompt("Entrez votre adresse e-mail :", "javascript@openclassrooms.
com");
if (/^([a-z0-9._-]+)@([a-z0-9._-]+)\.([a-z]{2,6})$/.test(email)) {
alert('Partie locale : ' + RegExp.$1 + '\nDomaine : ' + RegExp.$2 +
'\nExtension : ' + RegExp.$3);
} else {
alert('Adresse e-mail invalide !');
}
Remarquez que même si test() et exec() sont exécutés au sein d’un if(), le
contenu des parenthèses est quand même enregistré. Pas de changement de ce
côté-là puisque ces deux méthodes sont quand même exécutées au sein de la
condition.
Et ça fonctionne :
/<a href="(.+)">/.exec(html);
alert(RegExp.$1); // www.mon-adresse.be
/<a href="(.+?)">/.exec(html);
alert(RegExp.$1);
Rechercher et remplacer
Une fonctionnalité intéressante des regex est de pouvoir effectuer des rechercher-
remplacer : nous recherchons des portions de texte dans une chaîne de caractères et
nous les remplaçons par d’autres. Ceci s’avère relativement pratique pour modifier une
chaîne rapidement, ou pour convertir des données. Une utilisation fréquente est la
conversion de balises BBCode en HTML pour prévisualiser le contenu d’une zone de
texte.
Pour effectuer un rechercher-remplacer, il convient d’utiliser la méthode replace() de
l’objet String. Cette dernière reçoit deux arguments : la regex et une chaîne de
caractères qui sera le texte de remplacement. Petit exemple :
var sentence = 'Je m\'appelle Sébastien';
Rechercher et capturer
Il est possible d’utiliser les parenthèses capturantes pour extraire des informations et les
réutiliser au sein de la chaîne de remplacement. Par exemple, nous avons une date au
format américain : 05/26/2011, et nous souhaitons la convertir au format
jour/mois/année. Rien de plus simple :
var date = '05/26/2011';
alert(date); // Le 26/05/2011
Chaque nombre est capturé avec une parenthèse, et pour récupérer chaque parenthèse, il
suffit d’utiliser $1, $2 et $3 (directement dans la chaîne de caractères), exactement
comme nous l’aurions fait avec RegExp.$1.
alert(text);
Ici, nous avons utilisé [\s\S] et non pas uniquement le point. En effet, il s’agit de
trouver tous les caractères qui se trouvent entre les balises. Or, le point ne trouve que
des caractères et des espaces vides, hormis le retour à la ligne. C’est la raison pour
laquelle nous utiliserons souvent la classe comprenant \s et \S quand il s’agira de
trouver du texte comportant des retours à la ligne.
Cette petite regex de remplacement est la base d’un système de prévisualisation du
BBCode. Il suffit d’écrire une regex de ce type pour chaque balise et le tour est joué :
<script>
function preview() {
var value = document.getElementById("text").value;
document.getElementById("output").innerHTML = value;
}
</script>
<form>
<textarea id="text"></textarea><br />
<button type="button" onclick="preview()">Prévisualiser</button>
<div id="output"></div>
</form>
Pour illustrer cela, nous allons réaliser un petit script tout simple qui recherchera des
nombres dans une chaîne et les transformera en toutes lettres. La transformation se fera
au moyen de la fonction num2Letters() qui a été codée lors du TP présenté au chapitre
9 consistant à convertir un nombre en toutes lettres
(https://openclassrooms.com/courses/dynamisez-vos-sites-web-avec-javascript).
var sentence = 'Dans 22 jours, j\'aurai 24 ans';
if (!isNaN(p1)) {
return num2Letters(p1);
}
});
L’exemple utilise une fonction anonyme, mais il est bien évidemment possible de
déclarer une fonction :
function convertNumbers(str) {
str = parseInt(str);
if (!isNaN(str)) {
return num2Letters(str);
}
}
Autres recherches
Il reste deux méthodes de String à détailler : search() et match(). Nous allons
également revenir sur la méthode split().
La méthode alert() affiche donc un tableau contenant tous les prénoms, car il a été
demandé à split() de couper la chaîne dès que les caractères suivants sont trouvés : ,,
: et ;.
En résumé
• Construire une regex se fait rarement du premier coup. Il faut y aller par étapes,
morceau par morceau, car la syntaxe devient vite compliquée.
• En combinant les parenthèses capturantes et la méthode exec(), il est possible
d’extraire des informations.
• Les recherches doivent se faire en mode non greedy. C’est plus rapide et cela
correspond plus au comportement attendu.
• L’option g indique qu’il faut effectuer plusieurs remplacements, et non pas un seul.
• Il est possible d’utiliser une fonction pour la réalisation d’un remplacement. Ce n’est
utile que quand il est nécessaire de faire des opérations en même temps que le
remplacement.
• La méthode search() s’utilise comme la méthode indexOf(), sauf que le paramètre
est une regex.
21 Les données numériques
La gestion des données numériques en JavaScript est assez limitée mais elle existe
quand même et se fait essentiellement par le biais des objets Number et Math. Le
premier est assez inintéressant mais il est bon de savoir à quoi il sert. Math est quant à
lui une véritable boîte à outils qui vous servira probablement un jour ou l’autre.
L’objet Number
L’objet Number est à la base de tout nombre et pourtant on ne s’en sert quasiment jamais
de manière explicite, car on lui préfère (comme pour la plupart des objets) l’utilisation
de son type primitif. Cet objet possède pourtant des fonctions intéressantes, par
exemple, celle permettant de faire des conversions depuis une chaîne de caractères
jusqu’à un nombre en instanciant un nouvel objet Number :
var myNumber = new Number('3');
// La chaîne de caractères « 3 » est convertie en un nombre de valeur 3
Cependant, cette conversion est imprécise car nous ne savons pas si nous obtiendrons un
nombre entier ou flottant en retour. Aussi, nous lui préférons les fonctions parseInt()
et parseFloat() qui permettent d’être sûr du résultat. De plus, parseInt() utilise un
second argument permettant de spécifier la base (2 pour le système binaire, 10 pour le
système décimal, etc.) du nombre écrit dans la chaîne de caractères, ce qui permet de
lever tout soupçon sur le résultat obtenu.
Cet objet possède des propriétés accessibles directement sans aucune instanciation (on
appelle cela des propriétés propres à l’objet constructeur). Elles sont au nombre de
cinq, et sont données ici à titre informatif, car leur usage est peu courant :
• NaN : vous connaissez déjà cette propriété qui signifie Not a Number et qui permet,
généralement, d’identifier l’échec d’une conversion de chaîne de caractères en un
nombre. À noter que cette propriété est aussi disponible dans l’espace global. Passer
par l’objet Number pour y accéder n’a donc que peu d’intérêt, surtout qu’il est bien
rare d’utiliser cette propriété, car on lui préfère la fonction isNaN(), plus fiable.
• MAX_VALUE : cette propriété représente le nombre maximal pouvant être stocké dans
une variable en JavaScript. Cette constante peut changer selon la version du
JavaScript utilisée.
• MIN_VALUE : elle est identique à la constante MAX_VALUE sauf qu’il s’agit ici de la
valeur minimale.
• POSITIVE_INFINITY : constante représentant l’infini positif. Vous pouvez l’obtenir
en résultat d’un calcul si vous divisez une valeur positive par 0. Cependant, son
utilisation est rare, car on lui préfère la fonction isFinite(), plus fiable.
• NEGATIVE_INFINITY : identique à POSITIVE_INFINITY sauf qu’il s’agit ici de
l’infini négatif. Vous pouvez obtenir cette constante en résultat d’un calcul si vous
divisez une valeur négative par 0.
Passons donc aux essais :
var max = Number.MAX_VALUE, // Comme vous le voyez, nous n'instancions pas
// d'objet, comme pour un accès au « prototype »
inf = Number.POSITIVE_INFINITY;
L’objet Math
Pour commencer, deux précisions s’imposent.
• La liste des propriétés et méthodes ne sera pas exhaustive, consultez la
documentation
(https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Math) si
vous souhaitez tout connaître.
• Toutes les propriétés et méthodes de cet objet sont accessibles directement sans
aucune instanciation, on appelle cela des méthodes et des propriétés statiques.
Considérez donc cet objet plutôt comme un namespace.
Les propriétés
Les propriétés de l’objet Math sont des constantes qui définissent certaines valeurs
mathématiques comme le nombre pi (π) ou le nombre d’Euler (e). Nous ne parlons que
de ces deux constantes car les autres ne sont pas souvent utilisées en JavaScript. Pour
les utiliser, rien de bien compliqué :
alert(Math.PI); // Affiche la valeur du nombre pi
alert(Math.E); // Affiche la valeur du nombre d'Euler
Les propriétés de l’objet Math s’utilisent facilement donc il n’y a pas grand-chose à
vous apprendre dessus. En revanche, les méthodes sont nettement plus intéressantes !
Les méthodes
L’objet Math comporte de nombreuses méthodes permettant de faire divers calculs un
peu plus évolués qu’une simple division. Il existe des méthodes pour le calcul des
cosinus et sinus, des méthodes d’arrondi et de troncature, etc. Elles sont assez
nombreuses pour faire bon nombre d’applications pratiques.
Arrondir et tronquer
Vous aurez souvent besoin d’arrondir vos nombres en JavaScript, notamment si vous
réalisez des animations. Par exemple, il est impossible de dire à un élément HTML qu’il
fait 23,33 pixels de largeur car il faut un nombre entier. C’est pourquoi nous allons
aborder les méthodes floor(), ceil() et round().
• La méthode floor() retourne le plus grand entier inférieur ou égal à la valeur que
vous avez passée en paramètre :
Math.floor(33.15); // Retourne : 33
Math.floor(33.95); // Retourne : 33
Math.floor(34); // Retourne : 34
• La méthode ceil() retourne le plus petit entier supérieur ou égal à la valeur que
vous avez passée en paramètre :
Math.ceil(33.15); // Retourne : 34
Math.ceil(33.95); // Retourne : 34
Math.ceil(34); // Retourne : 34
Une méthode est également prévue pour le calcul de la racine carrée d’un nombre :
sqrt() (abréviation de square root) :
Math.sqrt(9); // Retourne : 3
if (!integer) {
return Math.random() * (max - min) + min;
} else {
return Math.floor(Math.random() * (max - min + 1) + min);
}
La fonction est prête à être utilisée ! Le troisième paramètre sert à définir si nous
souhaitons obtenir un nombre entier ou non.
(Essayez une adaptation de ce code : http://course.oc-static.com/ftp-
tutos/cours/javascript/part3/chap5/ex3.html)
Cette fonction est assez simple : nous soustrayons le nombre minimal au nombre
maximal, nous obtenons alors l’intervalle de valeurs qui n’a plus qu’à être multiplié au
nombre aléatoire (qui est compris entre 0 et 1). Le résultat obtenu sera alors compris
entre 0 et la valeur de l’intervalle. Il ne reste alors plus qu’à lui ajouter le nombre
minimal pour obtenir une valeur comprise entre notre minimum et notre maximum.
Les inclassables
Bien que les objets Number et Math implémentent l’essentiel des méthodes de gestion
des données numériques qui existent en JavaScript, certaines fonctions globales
permettent de faire quelques conversions et contrôles de données un peu plus poussés.
alert(parseInt(myString)); // Affiche : 0
alert(parseInt(myString, 10)); // Affiche : 8
Pourquoi cette différence de résultat ? La solution est simple : en l’absence d’un second
argument, les fonctions parseInt() et parseFloat() vont tenter de deviner la base
utilisée – et donc le système de numération associé – par le nombre écrit dans la chaîne
de caractères. Ici, la chaîne de caractères commence par un 0, ce qui est caractéristique
du système octal. Nous obtenons donc 0 en retour. Afin d’éviter d’éventuelles
conversions hasardeuses, il est toujours bon de spécifier le système de numération de
travail.
alert(isNaN(myNumber));
// Affiche « true », cette variable ne contient pas de nombre
Quant à isFinite(), cette fonction renvoie true si le nombre ne tend pas vers l’infini :
var myNumber = 1/0; // 1 divisé par 0 tend vers l'infini
alert(isFinite(myNumber));
// Affiche « false », ce nombre tend vers l'infini
Pourquoi utiliser ces deux fonctions ? Ne suffit-il pas de vérifier si la variable contient
la valeur NaN ou Infinity ?
Essayons pour NaN :
var myVar = "test";
if (myVar == NaN) {
alert('Cette variable ne contient pas de nombre.');
} else {
alert('Cette variable contient un nombre.');
}
Ainsi, la fonction isNaN() est utile car elle vérifie si votre variable était destinée à
contenir un nombre et si ce dernier ne possède pas la valeur NaN.
Concernant isFinite(), un nombre peut tendre soit vers l’infini positif, soit vers
l’infini négatif. Une condition de ce genre ne peut donc pas fonctionner :
var myNumber = -1 / 0;
if (myNumber == Number.POSITIVE_INFINITY) {
alert("Ce nombre tend vers l'infini.");
} else {
alert("Ce nombre ne tend pas vers l'infini.");
}
En résumé
• Un objet possède parfois des propriétés issues du constructeur. C’est le cas de
l’objet Number, avec des propriétés comme Number.MAX_VALUE ou Number.NaN.
• De même que Number, l’objet Math possède ce genre de propriétés. Utiliser π est
alors simple : Math.PI.
• Il faut privilégier parseInt() et parseFloat() pour convertir une chaîne de
caractères en un nombre.
• L’usage des propriétés de l’objet Number lors de tests est déconseillé : il vaut mieux
utiliser les fonctions globales prévues à cet effet, par exemple isNaN() au lieu de
NaN.
22 La gestion du temps
La gestion du temps est importante en JavaScript ! Elle vous permet de temporiser les
codes et donc de créer, par exemple, des animations, des compteurs à rebours et bien
d’autres choses qui nécessitent une temporisation dans un code.
Dans ce chapitre, nous verrons également comment manipuler la date et l’heure.
Le système de datation
L’heure et la date sont gérées par un seul et même objet : Date. Avant de l’étudier de
façon approfondie, nous allons d’abord étudier le fonctionnement du système de datation
en JavaScript.
Le constructeur
Commençons par le constructeur ! Ce dernier prend en paramètre de nombreux
arguments et s’utilise de différentes manières. Voici les quatre manières de l’utiliser :
new Date();
new Date(timestamp);
new Date(dateString);
new Date(année, mois, jour [, heure, minutes, secondes, millisecondes ]);
Cette date correspond au samedi 4 mai 1991 à 20 h précises, avec un décalage de deux
heures en plus par rapport à l’horloge de Greenwich.
Il existe plusieurs manières d’écrire la date, cependant nous ne fournissons que celle-là
car les dérives sont nombreuses. Si vous voulez connaître toutes les syntaxes existantes,
jetez un coup d’œil au document qui traite de la standardisation de cette syntaxe
(http://www.ietf.org/rfc/rfc3339.txt, la syntaxe est décrite à partir de la page 11).
Pour utiliser cette syntaxe avec notre méthode, rien de plus simple :
var timestamp = Date.parse('Sat, 04 May 1991 20:00:00 GMT+02:00');
Ces huit méthodes possèdent chacune une fonction homologue de type set.
Par exemple, avec la méthode getDay(), il existe aussi pour le même objet
Date, la méthode setDay() qui permet de définir le jour en le passant en
paramètre.
Chacune de ces méthodes s’utilise d’une manière enfantine : vous instanciez un objet
Date et il suffit ensuite d’appeler les méthodes souhaitées :
alert(myDate.getMonth()); // Affiche : 4
alert(myDate.getDay()); // Affiche : 6
Cette méthode est déconseillée car vous appelez ici implicitement la fonction eval()
qui va se charger d’analyser et d’exécuter votre chaîne de caractères. Pour de plus
amples informations, consultez l’excellent document intitulé « Bonnes pratiques
JavaScript » (https://openclassrooms.com/courses/bonnes-pratiques-javascript)
rédigé par nod_ (https://openclassrooms.com/membres/nod-42971).
En ce qui concerne le second paramètre, il n’y a pas grand-chose à dire mis à part qu’il
s’agit du temps à spécifier (en millisecondes) à votre fonction. Ce temps n’a que peu
d’intérêt à être en dessous de 10 ms (environ, cela dépend des navigateurs) pour la
simple et bonne raison que la plupart des navigateurs n’arriveront pas à exécuter votre
code avec un temps aussi court. En clair, si vous spécifiez un temps de 5 ms, votre code
sera probablement exécuté au bout de 10 ms.
Cependant, cette technique ne fonctionne pas sur les versions d’Internet Explorer
antérieures à la version 10. Nous devons donc ruser :
setTimeout(function() {
myFunction(param1, param2);
}, 2000);
Nous avons créé une fonction anonyme qui va se charger d’appeler la fonction finale
avec les bons paramètres, et cela fonctionne sur tous les navigateurs !
<script>
(function() {
button.addEventListener('click', function() {
clearTimeout(timerID); // Le compte à rebours est annulé
alert("Le compte à rebours a bien été annulé.");
// Et on prévient l'utilisateur
});
})();
</script>
button.addEventListener('click', function() {
clearTimeout(timerID); // On annule le compte à rebours
clearInterval(intervalID); // Et l'intervalle
alert("Le compte à rebours a bien été annulé.");
});
})();
</script>
<script>
var myImg = document.getElementById('myImg');
myImg.style.opacity = 0.2;
</script>
Avec ce code, l’opacité a été modifiée immédiatement de 1 à 0,2 alors que nous voulons
que le changement soit progressif. Il faudrait donc écrire le code suivant :
var myImg = document.getElementById('myImg');
Ici aussi, le changement n’a duré qu’une fraction de seconde ! Nous allons donc utiliser
les actions temporelles afin de temporiser notre code et de lui laisser le temps
d’afficher la progression à l’utilisateur. Dans notre cas, nous allons utiliser la fonction
setTimeout() :
function anim() {
var s = myImg.style,
result = s.opacity = parseFloat(s.opacity) - 0.1;
En résumé
• Le JavaScript se base sur un timestamp exprimé en millisecondes pour calculer les
dates. Il faut faire attention, car un timestamp est parfois exprimé en secondes,
comme au sein du système Unix ou dans des langages comme le PHP.
• En instanciant un objet Date sans paramètre, il contiendra la date de son
instanciation. Il est ensuite possible de définir une autre date par le biais de
méthodes ad hoc.
• Date est couramment utilisé pour déterminer le temps d’exécution d’un script. Il
suffit de soustraire le timestamp du début au timestamp de fin.
• Plusieurs fonctions existent pour créer des délais d’exécution et de répétition, ce qui
peut être utilisé pour réaliser des animations.
23 Les tableaux
Dans la première partie, nous vous avons présenté les tableaux de manière élémentaire.
Ce que vous y avez appris vous a sûrement suffi jusqu’à présent, mais il faut savoir que
les tableaux possèdent de nombreuses méthodes qui vous sont encore inconnues et qui
pourraient pourtant vous aider facilement à traiter leur contenu. Dans ce chapitre, nous
allons donc étudier de façon plus approfondie l’utilisation des tableaux.
L’objet Array
L’objet Array est à la base de tout tableau. Il possède toutes les méthodes et les
propriétés nécessaires à l’utilisation et à la modification des tableaux. Précisons que cet
objet ne concerne que les tableaux itératifs (les objets littéraux ne sont pas des tableaux
mais des objets, tout simplement).
Le constructeur
Cet objet peut être instancié de trois manières différentes. Cependant, gardez bien à
l’esprit que l’utilisation de son type primitif est préférable à l’instanciation de son
objet. Nous n’abordons ce sujet qu’à titre indicatif.
Les propriétés
Les tableaux simplifient beaucoup les choses car ils ne possèdent qu’une seule propriété
(accessible uniquement après instanciation) : length. Pour rappel, cette propriété est en
lecture seule et indique le nombre d’éléments contenus dans le tableau.
Ainsi, avec ce tableau :
var myArray = [
'élément1',
'élément2',
'élément3',
'élément4'
];
Les méthodes
Plusieurs méthodes ont déjà été abordées au chapitre 7 consacré aux objets et aux
tableaux. Nous allons approfondir ces notions dans ce chapitre qui vous servira de
référence.
Pas terrible, n’est-ce pas ? Heureusement, les tableaux possèdent une méthode nommée
concat() qui nous permet d’obtenir le résultat souhaité :
var myArray = ['test1', 'test2'].concat(['test3', 'test4']);
alert(myArray);
Notez bien que la méthode concat() ne modifie aucun tableau. Elle ne fait que
retourner un nouveau tableau qui correspond à la concaténation souhaitée.
Parcourir un tableau
Parcourir un tableau est une façon de faire très courante en programmation, que ce soit
en JavaScript ou dans un autre langage. Jusqu’à présent nous faisions ainsi :
var myArray = ["C'est", "un", "test"],
length = myArray.length;
Cette méthode ne fonctionne qu’avec des tableaux, elle n’existe pas pour les
collections d’éléments retournées par le DOM. Ainsi, cela ne fonctionne pas
sur la propriété classList ou encore les tableaux retournés par les méthodes
de sélection telles que querySelectorAll() ou getElementsByTagName().
alert(myArray.indexOf(element2)); // Affiche : 1
Dans ce code, c’est bien le tableau ['test'] qui a été trouvé, et non pas la chaîne de
caractères 'test'.
Pourquoi avoir créé la variable element2 ? Pour une raison bien simple :
alert(['test'] == ['test']); // Affiche : « false »
Les deux tableaux sont de même valeur mais sont pourtant reconnus comme étant deux
tableaux différents, tout simplement parce que ce ne sont pas les mêmes instanciations
de tableaux. Lorsque vous écrivez une première fois ['test'], vous faites une
première instanciation de tableau. Lors de la seconde écriture, vous effecuez donc une
seconde instanciation.
Pour être sûr de comparer deux instanciations identiques, il convient de passer la
référence de votre instanciation à une variable. Ainsi, vous n’avez plus aucun
problème :
var myArray = ['test'];
alert(myArray == myArray); // Affiche : « true »
Pour terminer sur nos deux fonctions, sachez qu’elles possèdent, elles aussi, un second
paramètre permettant de spécifier à partir de quel index vous souhaitez commencer la
recherche.
Trier un tableau
Deux méthodes peuvent vous servir pour trier un tableau.
La méthode reverse()
Cette méthode ne prend aucun argument en paramètre et ne retourne aucune valeur. Elle
ne fait qu’inverser l’ordre des valeurs d’un tableau :
var myArray = [1, 2, 3, 4, 5];
myArray.reverse();
La méthode sort()
Les choses se compliquent un peu ici. Par défaut, la méthode sort() trie un tableau par
ordre alphabétique uniquement. Mais elle possède aussi un argument facultatif
permettant de spécifier l’ordre à définir, et c’est là que les choses se compliquent. Tout
d’abord, prenons un exemple simple :
var myArray = [3, 1, 5, 10, 4, 2];
myArray.sort();
Quand nous disions que cette méthode ne triait par défaut que par ordre alphabétique,
c’était vrai et ce dans tous les cas ! Cette méthode possède en fait un mode de
fonctionnement bien particulier : elle commence par convertir toutes les données du
tableau en chaînes de caractères, puis elle trie par ordre alphabétique. Dans notre
exemple, la logique peut vous paraître obscure, mais si nous essayons de remplacer nos
chiffres par des caractères cela devrait vous paraître plus parlant :
0 = a ; 1 = b ; 2 = c
Notre suite « 1, 10, 2 » devient donc « b, ba, c ». Avec la méthode sort(), cette logique
s’applique même aux chiffres.
Venons-en maintenant à l’argument facultatif de sort() : il a pour but de réaliser un tri
personnalisé. Il doit contenir une référence vers une fonction que vous avez créée, cette
dernière devant posséder deux arguments qui seront spécifiés par la méthode sort().
La fonction devra alors dire si les valeurs transmises en paramètres sont de même
valeur, ou bien si l’une des deux est supérieure à l’autre.
Notre but ici est de faire en sorte que notre tri soit non pas alphabétique, mais par ordre
croissant (et donc que la valeur 10 se retrouve à la fin du tableau). Nous allons donc
commencer par créer notre fonction anonyme que nous fournirons au moment du tri :
function(a, b) {
// Comparaison des valeurs
}
Nous allons ensuite comparer les deux valeurs fournies. Avant tout, sachez que la
méthode sort() ne convertit pas les données du tableau en chaînes de caractères
lorsque vous avez défini l’argument facultatif, ce qui fait que les valeurs que nous allons
recevoir en paramètres seront bien de type Number et non pas de type String, ce qui
nous facilite déjà la tâche !
Commençons par écrire le code pour comparer les valeurs :
function(a, b) {
if (a < b) {
// La valeur de a est inférieure à celle de b
} else if (a > b) {
// La valeur de a est supérieure à celle de b
} else {
// Les deux valeurs sont égales
}
Nous avons donc fait nos comparaisons, mais que faut-il renvoyer à la méthode sort()
pour lui indiquer qu’une valeur est inférieure, supérieure ou égale à l’autre ?
Le principe est simple :
• la valeur -1 est retournée lorsque a est inférieur à b ;
• la valeur 1 est retournée lorsque a est supérieur à b ;
• la valeur 0 est retournée quand les valeurs sont égales.
Notre fonction devient donc la suivante :
function(a, b) {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
myArray.sort(function (a, b) {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
});
Notons aussi que le second argument possède une petite particularité intéressante qui
peut rappeler le PHP aux connaisseurs :
var myArray = [1, 2, 3, 4, 5];
myArray.splice(1, 1, 2, 3);
À noter que si vous ajoutez des éléments dans le tableau, vous pouvez mettre le
deuxième argument à 0, ce qui aura pour effet d’ajouter des éléments sans être obligé
d’en supprimer d’autres. La méthode splice() peut donc être utilisée comme une
méthode d’insertion de données.
Cette méthode n’est pas supportée par les versions d’Internet Explorer
antérieures à la version 9.
Les piles et les files
Nous allons aborder ici un concept que vous avez déjà rapidement étudié dans ce cours,
mais qu’il serait bon de se remémorer.
Les piles et les files sont deux manières de manipuler vos tableaux. Plutôt que de les
voir comme de simples listes de données, vous pouvez les imaginer comme étant, par
exemple, une pile de livres où le dernier posé sera au final le premier récupéré, ou bien
comme une file d’attente, où le dernier entré sera le dernier sorti. Ces deux façons de
faire sont bien souvent très pratiques dans de nombreux cas, vous vous en rendrez bien
vite compte.
Les piles
Le principe est que le premier élément ajouté sera le dernier retiré. Elles peuvent être
utilisées de deux manières : soit avec les deux méthodes push()et pop(), soit avec les
méthodes unshift() et shift(). Dans le premier cas, la pile sera empilée et dépilée
à la fin du tableau, dans le second cas, les opérations se feront au début du tableau.
var myArray = ['Livre 1'];
result = myArray.pop();
Aucun problème pour les méthodes push() et pop(). Essayons maintenant le couple
unshift()/ shift() :
result = myArray.shift();
Les files
Le principe est différent ici mais tout aussi simple : le premier élément ajouté est le
premier sorti. Les piles s’utilisent également de deux manières : avec le couple push()
/shift() ou avec le couple unshift()/ pop().
En JavaScript, les files sont bien moins utilisées que les piles, car elles sont
dépendantes des méthodes unshift() et shift(), qui souffrent d’un manque
de performance comme nous le verrons dans la section suivante.
result = myArray.shift();
result = myArray.pop();
Si le tableau possède de nombreux éléments, cela peut parfois prendre un peu de temps.
C’est pourquoi les piles sont généralement préférées aux files en JavaScript, car elles
peuvent se passer de ces deux méthodes. Cela dit, il faut relativiser : la perte de
performance n’est pas dramatique. Vous pouvez très bien vous en servir pour des
tableaux de petite taille (en dessous de 10 000 entrées environ). Pour les tableaux plus
importants, il faudra peut-être songer à utiliser les piles ou des scripts permettant de
résoudre ce genre de problème.
En résumé
• Pour concaténer deux tableaux, il faut utiliser la méthode concat() car l’opérateur +
ne fonctionne pas selon le comportement voulu.
• La méthode forEach() permet de parcourir un tableau en s’affranchissant d’une
boucle for.
• indexOf() et lastIndexOf() permettent de rechercher un élément qui peut être une
chaîne de caractères, un nombre, ou même un tableau. Il faudra toutefois faire
attention lors de la comparaison de deux tableaux.
• L’utilisation d’une fonction pour trier un tableau est possible et se révèle
particulièrement utile pour effectuer un tri personnalisé.
• Les piles et les files sont un moyen efficace pour stocker et accéder à de grandes
quantités de données.
24 Les images
Les objets natifs en JavaScript couvrent de nombreux domaines comme le temps ou les
mathématiques, mais il existe des objets encore plus particuliers comme Image. Celui-
ci permet de faire des manipulations assez sommaires sur une image et surtout de savoir
si elle a été entièrement téléchargée (principale utilisation de Image).
L’objet Image
Nous utiliserons donc cet objet pour manipuler des images. Image possède plusieurs
propriétés permettant d’obtenir divers renseignements sur l’image actuellement
instanciée.
Le constructeur
Le constructeur de l’objet Image ne prend aucun argument en paramètre, cela a au moins
le mérite d’être simple :
var myImg = new Image();
Les propriétés
Voici une liste non exhaustive des propriétés de l’objet Image. Consultez la
documentation (https://developer.mozilla.org/en/DOM/HTMLImageElement) pour une
liste complète (mais pas forcément fiable).
NOM DE LA PROPRIÉTÉ CONTIENT…
Contient la largeur originale de l’image. Vous pouvez
width
redéfinir cette propriété pour modifier la taille de l’image.
Contient la hauteur originale de l’image. Vous pouvez
height
redéfinir cette propriété pour modifier la taille de l’image.
Cette propriété permet de spécifier l’adresse (absolue ou
relative) de l’image. Une fois que cette propriété est
src
spécifiée, l’image commence immédiatement à être
chargée.
Il existe une propriété nommée complete qui permet de savoir si l’image a été
entièrement chargée. Cependant, cette propriété n’est pas standard (sauf en
HTML 5) et son implémentation est assez hasardeuse. Elle fonctionne parfois,
mais la plupart du temps nous obtenons une valeur erronée. Nous en
déconseillons donc l’utilisation.
Événements
L’objet Image ne possède qu’un seul événement nommé load, il est très utile,
notamment lorsqu’il s’agit de créer un script de type Lightbox
(http://en.wikipedia.org/wiki/Lightbox_%28JavaScript%29) car il permet de savoir
quand une image est chargée.
Son utilisation est similaire à celle de n’importe quel événement :
var myImg = new Image();
myImg.src = 'adresse_de_mon_image';
myImg.addEventListener('load', function() {
// Etc.
});
Ainsi, vous n’aurez aucun problème : votre événement sera toujours déclenché !
Particularités
L’objet Image est un peu spécial car on peut l’ajouter à votre arbre DOM comme vous
le feriez avec la valeur retournée par la méthode document.createElement(). Ce
comportement est particulier et ne s’avère utile que dans de très rares cas d’application.
Il est toutefois préférable de vous en parler afin que vous connaissiez l’astuce :
var myImg = new Image();
myImg.src = 'adresse_de_mon_image';
Mise en pratique
Nous allons mettre en pratique l’objet Image en réalisant une Lightbox très simple. Le
principe d’une Lightbox est de permettre l’affichage d’une image en taille réelle
directement dans la page web où se trouvent les miniatures de toutes nos images.
Pour réaliser cet exercice, vous devez posséder quelques images afin de tester
les codes. Plutôt que de vous embêter à chercher des images, les
redimensionner, puis les renommer, vous pouvez utiliser notre pack d’images
toutes prêtes (à télécharger ici : http://course.oc-static.com/ftp-
tutos/cours/javascript/part3/chap8/imgs.zip).
Commençons tout d’abord par un code HTML simple pour lister nos miniatures et les
liens vers les images originales :
<p>
<a href="imgs/1.jpg" title="Afficher l'image originale">
<img src="imgs/1_min.jpg" alt="Miniature" /></a>
<a href="imgs/2.jpg" title="Afficher l'image originale">
<img src="imgs/2_min.jpg" alt="Miniature" /></a>
<a href="imgs/3.jpg" title="Afficher l'image originale">
<img src="imgs/3_min.jpg" alt="Miniature" /></a>
<a href="imgs/4.jpg" title="Afficher l'image originale">
<img src="imgs/4_min.jpg" alt="Miniature" /></a>
</p>
Notre but ici est de bloquer la redirection des liens et d’afficher les images d’origine
directement dans la page web plutôt que dans une nouvelle page. Pour cela, nous allons
devoir parcourir tous les liens de la page, bloquer leurs redirections et afficher l’image
d’origine une fois que celle-ci aura fini d’être chargée (car une Lightbox est aussi
conçue pour embellir la navigation).
links[i].addEventListener('click', function(e) {
e.preventDefault(); // On bloque la redirection
Vous pouvez constater que nous faisons appel à la fonction displayImg() qui n’existe
pas, nous allons donc la créer !
Cette fonction doit tout d’abord charger l’image originale avant de l’afficher :
function displayImg(link) {
img.addEventListener('load', function() {
// Affichage de l'image
});
img.src = link.href;
Nous lui appliquons un style CSS afin qu’il puisse couvrir toute la page web :
#overlay {
display : none; /* Par défaut, on cache l'overlay */
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
text-align: center; /* Pour centrer l'image que l'overlay contiendra */
À présent, nous allons afficher l’overlay lorsqu’une miniature sera cliquée. Il faudra
aussi afficher un petit message pour faire patienter le visiteur pendant le chargement de
l’image. Une fois l’image chargée, il ne restera plus qu’à supprimer le texte et à ajouter
l’image originale à la place.
function displayImg(link) {
img.addEventListener('load', function() {
overlay.innerHTML = '';
overlay.appendChild(img);
});
img.src = link.href;
overlay.style.display = 'block';
overlay.innerHTML = '<span>Chargement en cours...</span>';
L’image se charge et s’affiche, mais nous ne pouvons pas fermer l’overlay pour choisir
une autre image. Pour cela, il suffit de quitter l’overlay lorsque nous cliquons quelque
part dessus :
document.getElementById('overlay').addEventListener('click', function(e) {
// currentTarget est utilisé pour cibler l'overlay et non l'image
e.currentTarget.style.display = 'none';
});
Il ne nous reste plus qu’à ajouter un peu de CSS pour embellir le tout, et c’est fini :
#overlay img {
margin-top: 100px;
}
p {
margin-top: 300px;
text-align: center;
}
<div id="overlay"></div>
#overlay {
display : none; /* Par défaut, on cache l'overlay */
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-align: center; /* Pour centrer l'image que l'overlay contiendra */
#overlay img {
margin-top: 100px;
}
p {
margin-top: 300px;
text-align: center;
}
var links = document.getElementsByTagName('a'),
linksLen = links.length;
links[i].addEventListener('click', function(e) {
e.preventDefault(); // On bloque la redirection
function displayImg(link) {
img.addEventListener('load', function() {
overlay.innerHTML = '';
overlay.appendChild(img);
});
img.src = link.href;
overlay.style.display = 'block';
overlay.innerHTML = '<span>Chargement en cours...</span>';
document.getElementById('overlay').addEventListener('click', function(e) {
// currentTarget est utilisé pour cibler l'overlay et non l'image
e.currentTarget.style.display = 'none';
});
Comme nous l’avons dit précédemment, ce script est actuellement inutilisable sur un site
en production. Cependant, si vous souhaitez améliorer ce code afin de le publier, nous
vous conseillons d’étudier ces quelques points.
• Évitez d’appliquer l’événement click à tous les liens de la page. Ajoutez un élément
différenciateur aux liens de la Lightbox, par exemple une classe ou un attribut name.
• Redimensionnez dynamiquement les images originales pour qu’elles ne débordent
pas de la page. Utilisez soit un redimensionnement fixe (très simple à faire), soit un
redimensionnement variant selon la taille de l’écran (des recherches sur le Web
seront nécessaires).
• Implémentez l’utilisation des touches fléchées : flèche droite pour l’image suivante,
flèche gauche pour la précédente.
• Faites donc quelque chose de plus beau, notre exemple n’est vraiment pas beau !
En résumé
• L’objet Image est généralement utilisé pour s’assurer qu’une image a été chargée, en
utilisant l’événement load().
• Il est possible d’ajouter un objet Image directement dans l’arbre DOM, mais ce n’est
pas chose courante.
25 Les polyfills et les wrappers
La solution
Il existe un moyen de se faciliter plus ou moins la tâche : les polyfills ! Concrètement,
un polyfill est un script qui a pour but de fournir une technologie à tous les navigateurs
existants. Une fois implémenté dans votre code, un polyfill a deux manières de réagir.
• Le navigateur est récent et supporte la technologie souhaitée, le polyfill ne va alors
strictement rien faire et va vous laisser utiliser cette technologie comme elle devrait
l’être nativement.
• Le navigateur est trop vieux et ne supporte pas la technologie souhaitée, le polyfill va
alors « imiter » cette technologie grâce à diverses astuces et vous permettra de
l’utiliser comme si elle était disponible nativement.
Rien ne vaut un bon exemple pour comprendre le principe ! Essayez donc le script
suivant avec votre navigateur habituel (qui se doit d’être récent), puis sur un vieux
navigateur ne supportant pas la méthode isArray(), Internet Explorer 8 fera très bien
l’affaire :
if (!Array.isArray) { // Si isArray() n'existe pas, alors on crée notre
// méthode alternative :
Array.isArray = function(element) {
return Object.prototype.toString.call(element) == '[object Array]';
};
}
Voici un petit rappel de ce qui a été dit au début de la troisième partie : « En théorie,
chaque objet peut se voir attribuer des méthodes via prototype. Mais en pratique, si
cela fonctionne avec les objets natifs génériques comme String, Date, Array, Object,
Number, Boolean et de nombreux autres, cela fonctionne moins bien avec les objets
natifs liés au DOM comme Node, Element ou encore HTMLElement, en particulier dans
Internet Explorer. »
De plus, la modification d’un objet natif est plutôt déconseillée au final, car vous
risquez de modifier une méthode déjà existante. Nous avons donc besoin de méthodes et
de propriétés supplémentaires, mais comment les ajouter sans risque ?
La solution
Il existe une solution nommée wrapper, qui consiste en un code ayant pour but
d’encadrer l’utilisation de certains éléments du JavaScript. Il peut ainsi contrôler la
manière dont ils sont employés et réagir en conséquence pour fournir des fonctionnalités
supplémentaires aux développeurs.
Lorsque nous avions abordé l’objet Image, la propriété complete avait été évoquée
mais non étudiée en raison de son comportement hasardeux. Nous allons essayer de
permettre son support.
Le développement d’un wrapper permet d’encadrer l’utilisation de certains éléments,
comme nous l’avons dit. Il s’agit en fait d’une surcouche qui va servir à contrôler nos
éléments. Dans l’idéal, un wrapper doit permettre au développeur de se passer de
l’élément original, ainsi le travail ne s’effectuera que par le biais de la surcouche que
constitue le wrapper.
Puisque notre wrapper doit servir de surcouche de A à Z, celui-ci va se présenter sous
forme d’objet qui sera instancié à la place de l’objet Image :
function Img() {
var obj = this; // Nous faisons une petite référence vers notre objet
// Img. Cela nous facilitera la tâche.
this.originalImg = new Image(); // On instancie l'objet original, le
// wrapper servira alors d'intermédiaire
Notre but étant de permettre le support de la propriété complete, nous allons devoir
créer par défaut un événement load qui se chargera de modifier cette propriété lors de
son exécution. Il nous faut aussi assurer le support de l’événement load pour les
développeurs :
function Img() {
var obj = this; // Nous faisons une petite référence vers notre objet
// Img. Cela nous facilitera la tâche.
this.complete = false;
this.onload = function() {}; // Voici l'événement que les développeurs
// pourront modifier
this.originalImg.onload = function() {
};
Afin de ne pas trop complexifier ce chapitre, nous utilisons ici le DOM-0 pour
attacher nos événements, mais l’utilisation du DOM-2 reste recommandée et
parfaitement applicable aux exemples présentés.
Actuellement, notre wrapper effectue ce que nous voulions qu’il fasse : assurer un
support de la propriété complete. Cependant, il nous est actuellement impossible de
spécifier les propriétés standards de l’objet original sans passer par notre propriété
originalImg. Or ce n’est pas ce que nous souhaitons car le développeur pourrait
compromettre le fonctionnement de notre wrapper, par exemple en modifiant la
propriété onload de l’objet original. Il va donc falloir créer une méthode permettant
l’accès à ces propriétés sans passer par l’objet original.
Nous ajoutons les méthodes set() et get() assurant le support des propriétés
d’origine :
Img.prototype.set = function(name, value) {
if (allowed.indexOf(name) != -1) {
this.originalImg[name] = value;
// Si la propriété est autorisée alors on la modifie
}
};
Img.prototype.get = function(name) {
return this.originalImg[name];
// Pas besoin de contrôle tant qu'il ne s'agit pas d'une modification
};
Nous voici maintenant avec un wrapper relativement complet qui possède cependant
une certaine absurdité : l’accès aux propriétés de l’objet d’origine se fait par le biais
des méthodes set() et get(), tandis que l’accès aux propriétés relatives au wrapper se
fait sans ces méthodes. Le principe est plutôt stupide vu qu’un wrapper a pour but d’être
une surcouche transparente. La solution pourrait donc être la suivante : faire passer les
modifications/lectures des propriétés par les méthodes set() et get() dans tous les
cas, y compris lorsqu’il s’agit de propriétés appartenant au wrapper.
Mettons cela en place :
Img.prototype.set = function(name, value) {
if (allowed.indexOf(name) != -1) {
this.originalImg[name] = value;
// Si la propriété est autorisée alors on la modifie
} else if (wrapperProperties.indexOf(name) != -1) {
this[name] = value; // Ici, la propriété appartient au wrapper et
// non pas à l'objet original
}
};
Img.prototype.get = function(name) {
};
Nous approchons grandement du code final. Il nous reste maintenant une dernière chose
à mettre en place qui peut se révéler pratique : pouvoir spécifier l’adresse de l’image
dès l’instanciation de l’objet. La modification est simple :
function Img(src) { // On ajoute un paramètre « src »
var obj = this; // Nous faisons une petite référence vers notre objet
// Img. Cela nous facilitera la tâche.
this.complete = false;
this.onload = function() {}; // Voici l'événement que les développeurs
// pourront modifier
this.originalImg.onload = function() {
};
if (src) {
this.originalImg.src = src; // Si elle est spécifiée, on définit
// alors la propriété src
}
var obj = this; // Nous faisons une petite référence vers notre objet
// Img. Cela nous facilitera la tâche.
this.complete = false;
this.onload = function() {}; // Voici l'événement que les développeurs
// pourront modifier
this.originalImg.onload = function() {
};
if (src) {
this.originalImg.src = src; // Si elle est spécifiée, on définit
// alors la propriété src
}
if (allowed.indexOf(name) != -1) {
this.originalImg[name] = value;
// Si la propriété est autorisée alors on la modifie
} else if (wrapperProperties.indexOf(name) != -1) {
this[name] = value;
// Ici, la propriété appartient au wrapper et non pas à l'objet original
}
};
Img.prototype.get = function(name) {
};
myImg.set('onload', function() {
// Affichage de diverses informations une fois l'image chargée
alert(
'complete : ' + this.get('complete') + '\n' +
'width : ' + this.get('width') + ' px\n' +
'height : ' + this.get('height') + ' px'
);
});
myImg.set('src', 'http://www.sdz-files.com/cours/javascript/part3/chap9/img.png'); //
On spécifie l'adresse de l'image
En résumé
• Les polyfills sont un moyen de s’assurer de la prise en charge d’une méthode si
celle-ci n’est pas supportée par le navigateur, et ce sans intervenir dans le code
principal. C’est donc totalement transparent.
• Les wrappers permettent d’ajouter des propriétés ou des méthodes aux objets, en
particulier les objets natifs, en créant un objet dérivé de l’objet en question.
26 Les closures
Vous avez probablement constaté que les fonctions anonymes sont très fréquemment
utilisées, comme pour les événements, les isolements de code, etc. Leurs utilisations
sont nombreuses et variées, car elles sont très facilement adaptables à toutes les
situations. Et s’il y a bien un domaine où les fonctions anonymes excellent, c’est bien
les closures !
alert(myVar);
Même sans l’exécuter, vous vous doutez sûrement du résultat que nous allons obtenir :
une erreur. Ceci est normal car myVar est déclarée dans une fonction tandis que nous
essayons d’y accéder depuis l’espace global (pour rappel, nous vous invitons à relire ce
document : https://openclassrooms.com/courses/dynamisez-vos-sites-web-avec-
javascript#ss_part_2).
La seule fonction capable d’accéder à myVar est area(), car c’est elle qui l’a créée.
Seulement, une fois l’exécution de la fonction terminée, la variable est supprimée et
devient donc inaccessible.
Maintenant, si nous faisons ceci :
function area() {
var myVar = 1;
function show() {
alert(myVar);
}
area();
alert(myVar);
Le résultat est toujours le même, il est nul. Cependant, en plus de la fonction area(), la
fonction show() est maintenant capable, elle aussi, d’accéder à myVar car elle a été
créée dans le même espace que celui de myVar. Mais pour cela il faudrait l’exécuter.
Plutôt que de l’exécuter immédiatement, nous allons l’exécuter une seconde après
l’exécution de notre fonction area(), ce qui devrait normalement retourner une erreur
puisque myVar est censée être détruite une fois qu’area() a terminé son exécution.
function area() {
var myVar = 1;
function show() {
alert(myVar);
}
setTimeout(show, 1000);
area();
Tout ce qui suit est très théorique et ne reflète pas forcément la véritable
manière dont les variables sont gérées.
Comprendre le problème
Les closures ont été conçues pour des raisons bien précises. Les problèmes qu’elles
sont supposées résoudre ne sont pas simples à comprendre, nous allons tâcher de vous
expliquer cela au mieux.
Premier exemple
Commençons par un exemple simple qui vous donnera un aperçu de l’ampleur du
problème :
var number = 1;
setTimeout(function() {
alert(number);
}, 100);
number++;
Un cas concret
Admettons que vous souhaitiez faire apparaître une dizaine de balises <div> de manière
progressive, les unes à la suite des autres. Voici le code que écririez probablement dans
l’état actuel de vos connaissances :
var divs = document.getElementsByTagName('div'),
divsLen = divs.length;
setTimeout(function() {
divs[i].style.display = 'block';
}, 200 * i); // Le temps augmentera de 200 ms à chaque élément
setTimeout(function() {
divs[i].style.display = 'block';
}, 200 * i);
Actuellement, le problème est que la variable i change de valeur avant même que nous
n’ayons eu le temps d’agir. Le seul moyen serait donc d’enregistrer cette valeur quelque
part. Essayons :
1. var divs = document.getElementsByTagName('div'),
2. divsLen = divs.length;
3.
4. for (var i = 0; i < divsLen; i++) {
5.
6. var currentI = i; // Déclarer une variable DANS une boucle n'est pas
// conseillé, ici c'est juste pour l'exemple
7.
8. setTimeout(function() {
9. divs[currentI].style.display = 'block';
10. }, 200 * i);
11.
12. }
Ici nous avons un cas parfait illustrant ce que nous avons étudié précédemment au sujet
des variables : currentI est déclarée dans une IIFE, sa référence est donc détruite à la
fin de l’exécution de l’IIFE. Cependant, nous y avons toujours accès dans notre fonction
anonyme exécutée en différé, car nous possédons une référence vers cette variable, ce
qui évite sa suppression.
Une dernière chose, vous risquez de rencontrer assez fréquemment des closures écrites
de cette manière :
1. var divs = document.getElementsByTagName('div'),
2. divsLen = divs.length;
3.
4. for (var i = 0; i < divsLen; i++) {
5.
6. (function(currentI) {
7.
8. setTimeout(function() {
9. divs[currentI].style.display = 'block';
10. }, 200 * i);
11.
12. })(i);
13.
14. }
Ici, nous avons tout simplement créé un argument currentI pour notre IIFE et nous lui
passons en paramètre la valeur de i. Cette modification fait gagner un peu d’espace
(suppression de la ligne 8) et permet de mieux organiser le code, on distingue plus
facilement ce qui constitue la closure ou non.
Nous pouvons apporter une modification supplémentaire à la ligne 6 :
var divs = document.getElementsByTagName('div'),
divsLen = divs.length;
(function(i) {
setTimeout(function() {
divs[i].style.display = 'block';
}, 200 * i);
})(i);
function display(value) {
alert(myVar);
display(); // Affiche : 0
display(42); // Affiche : 42
display(); // Affiche : 42
Alors que nous voudrions arriver à ceci afin d’éviter l’accès à myVar par une fonction
autre que display() :
function display(value) {
alert(myVar);
display(); // Affiche : 0
display(42); // Affiche : 42
display(); // Affiche : 42
var myVar = 0;
function() {
// Du code…
}
})();
return function() {
// Du code…
};
})();
Si nous reprenons notre exemple, adapté de manière à ce qu’il possède une variable
statique, nous obtenons ceci :
var display = (function() {
return function(value) {
alert(myVar);
};
})();
display(); // Affiche : 0
display(42); // Affiche : 42
display(); // Affiche : 42
En résumé
• Une variable peut posséder plusieurs références. Elle ne sera jamais supprimée tant
qu’elle possédera encore une référence active.
• Les closures ont été inventées dans le but de répondre à plusieurs problématiques
concernant la gestion de données.
• Une closure peut être écrite de manières différentes, à vous de choisir celle qui
convient le mieux à votre code.
Quatrième partie
L’échange de données avec Ajax
De nos jours, il n’est pas rare de voir une page web collecter de nouvelles informations
auprès d’un serveur sans nécessiter le moindre rechargement (comme le site
OpenClassrooms). Dans cette partie, nous allons étudier Ajax, ses avantages et sa mise
en place. Vous constaterez rapidement qu’il devient difficile de se passer de ce genre de
technologies.
27 Qu’est-ce que l’Ajax ?
Ajax offre de vastes possibilités que n’allons pas étudier intégralement, mais
uniquement les aspects principaux, les avantages et les inconvénients. Nous verrons
aussi quelles sont les technologies employées pour le transfert de données, qui vous
permettront d’étendre les utilisations de vos scripts.
Introduction au concept
Présentation
Ajax signifie Asynchronous JavaScript and XML, soit « JavaScript et XML
asynchrones ». Derrière ce nom se cache un ensemble de technologies destinées à
réaliser de rapides mises à jour du contenu d’une page web, sans qu’elles nécessitent le
moindre rechargement visible par l’utilisateur de la page web. Les technologies
employées sont diverses et dépendent du type de requête que nous souhaitons utiliser,
mais d’une manière générale, le JavaScript est constamment présent.
D’autres langages sont bien entendu pris en compte comme le HTML et le CSS, qui
servent à l’affichage, mais ceux-ci ne sont pas inclus dans le processus de
communication. Le transfert de données est géré exclusivement par le JavaScript, et
utilise certaines technologies de formatage de données comme le XML ou le JSON.
L’Ajax est un vaste domaine, dans le sens où les manières de charger un contenu sont
nombreuses. Nous verrons les techniques les plus courantes dans les chapitres suivants,
mais tout ne sera pas abordé.
Fonctionnement
À quoi peut bien servir l’Ajax ? Le rafraîchissement complet de la page n’est-il pas
plus simple ? Eh bien, cela dépend des cas d’applications !
Prenons l’exemple d’OpenClassrooms ! Ce site utilise l’Ajax pour plusieurs de ses
technologies. Nous étudierons deux d’entre elles et nous verrons pourquoi nous avons
besoin de l’Ajax pour les faire fonctionner correctement.
• L’autocomplétion. Lorsque vous recherchez un membre et que vous tapez les
premières lettres de son pseudo dans le formulaire prévu à cet effet, vous obtenez
une liste des membres dont le pseudo commence par les caractères que vous avez
spécifiés. Ce système requiert de l’Ajax pour la simple et bonne raison qu’il faut
demander au serveur de chercher les membres correspondant à la recherche, et ce
sans recharger la page, car les caractères entrés seraient alors perdus et l’ergonomie
serait plus que douteuse.
• La sauvegarde automatique des textes. OpenClassrooms intègre un outil très
pratique : tout texte écrit sur un cours, une news, ou même un simple message sur le
forum, est sauvegardé à intervalles réguliers dans une sorte de bloc-notes. Cette
sauvegarde doit se faire de manière transparente afin de ne pas gêner le rédacteur. Le
rechargement complet d’une page web n’est donc pas envisageable. C’est donc là
qu’intervient l’Ajax en permettant à votre navigateur d’envoyer tout votre texte au
serveur sans vous gêner.
Dans ces deux cas, les requêtes ne sont pas superflues, elles contiennent juste les
données à faire transiter, rien de plus. Et c’est là que réside l’intérêt de l’Ajax : les
requêtes doivent être rapides. Par exemple, pour obtenir la liste des membres, la
requête Ajax ne va pas recevoir une page complète d’OpenClassrooms (bannière, menu,
contenu, etc.). Elle va juste obtenir une liste des membres formatée de manière à
pouvoir l’analyser facilement.
Utilisation
Les formats classiques
Il s’agit ici du format texte et du HTML. Ces deux formats ne nécessitent aucun
traitement, vous récupérez le contenu et vous l’affichez à l’emplacement souhaité. Par
exemple, si vous recevez le texte suivant :
Je suis une alerte à afficher sur l'écran de l'utilisateur.
Que faire de plus que l’afficher cela à l’endroit approprié ? Il en va de même pour le
HTML :
<p>Je suis un paragraphe <strong>inintéressant</strong> qui doit être copié quelque
part dans le DOM.</p>
Que peut-on faire, à part copier ce code HTML là où il devrait être ? Le texte étant déjà
formaté sous sa forme finale, il n’y a aucun traitement à effectuer, il est prêt à l’emploi
en quelque sorte.
Le XML
Le format XML est déjà plus intéressant car il permet de structurer des données de la
même manière qu’en HTML, mais avec des balises personnalisées. Si vous ne
connaissez pas du tout le XML, il est conseillé de jeter un coup d’œil au cours
d’OpenClassrooms intitulé « Le point sur XML »
(https://openclassrooms.com/courses/le-point-sur-xml) et rédigé par Tangui
(https://openclassrooms.com/membres/tangui-94820) avant de continuer.
Le XML vous permet de structurer un document comme bon vous semble, tout comme en
HTML, mais avec des noms de balises personnalisés. Il est donc possible de réduire
drastiquement le poids d’un transfert, simplement en utilisant des noms de balises plutôt
courts. Par exemple, nous avons ici la représentation d’un tableau grâce au XML :
<?xml version="1.0" encoding="utf-8"?>
<table>
<line>
<cel>Ligne 1 - Colonne 1</cel>
<cel>Ligne 1 - Colonne 2</cel>
<cel>Ligne 1 - Colonne 3</cel>
</line>
<line>
<cel>Ligne 2 - Colonne 1</cel>
<cel>Ligne 2 - Colonne 2</cel>
<cel>Ligne 2 - Colonne 3</cel>
</line>
<line>
<cel>Ligne 3 - Colonne 1</cel>
<cel>Ligne 3 - Colonne 2</cel>
<cel>Ligne 3 - Colonne 3</cel>
</line>
</table>
L’utilisation du XML est intéressante dans la mesure où lorsque vous utilisez la requête
appropriée, vous pouvez parcourir le code XML avec les mêmes méthodes que celle
employées pour le DOM HTML, comme getElementsByTagName().
En effet, suite à votre requête, le code JavaScript va recevoir une chaîne de caractères
contenant un code comme celui du tableau précédent. À ce stade, il n’est pas encore
possible de parcourir ce code, car il ne s’agit que d’une chaîne de caractères.
Cependant, une fois la requête terminée et toutes les données reçues, un parseur
(http://fr.wikipedia.org/wiki/Analyse_syntaxique), ou analyseur syntaxique, va se
déclencher pour analyser le code reçu, le décomposer et enfin le reconstituer sous forme
d’arbre DOM qu’il sera possible de parcourir.
Ainsi, nous pouvons très bien compter le nombre de cellules (les balises <cel>) qui
existent et voir leur contenu grâce aux méthodes que nous sommes habitués à utiliser
avec le DOM HTML. Nous verrons cela dans le chapitre suivant.
Le JSON
Le JSON (JavaScript Object Notation) est le format le plus utilisé et le plus pratique
pour nous. Comme son nom l’indique, il s’agit d’une représentation des données sous
forme d’objet JavaScript. Essayons, par exemple, de représenter une liste de membres
ainsi que leurs informations respectives :
{
Membre1: {
posts: 6230,
inscription: '22/08/2003'
},
Membre2: {
posts: 200,
inscription: '04/06/2011'
}
Cela ne vous dit rien ? Il s’agit pourtant d’un objet classique, comme ceux auxquels
vous êtes habitués ! Tout comme avec le XML, vous recevez ce code sous forme de
chaîne de caractères. Cependant, le parseur ne se déclenche pas automatiquement pour
ce format. Il faut utiliser l’objet nommé JSON, qui possède deux méthodes bien
pratiques.
• La première, parse(), prend en paramètre la chaîne de caractères à analyser et
retourne le résultat sous forme d’objet JSON.
• La seconde, stringify(), permet de faire l’inverse : elle prend en paramètre un
objet JSON et retourne son équivalent sous forme de chaîne de caractères.
Voici un exemple d’utilisation de ces deux méthodes :
var obj = {
index: 'contenu'
},
string;
string = JSON.stringify(obj);
obj = JSON.parse(string);
Le JSON est très pratique pour recevoir des données, mais aussi pour en envoyer,
surtout depuis que le PHP 5.2 permet le support des fonctions json_decode()
(http://fr2.php.net/manual/fr/function.json-decode.php) et json_encode()
(http://fr2.php.net/manual/fr/function.json-encode.php).
En résumé
• L’Ajax est un moyen de charger des données sans recharger la page en utilisant le
JavaScript.
• Dans une requête Ajax, les deux formats de données plébiscités sont le XML et le
JSON, mais les données au format texte sont permises.
• Les données reçues au format XML ont l’avantage de pouvoir être traitées avec des
méthodes DOM, comme getElementById(). L’inconvénient est que le XML peut se
révéler assez verbeux, ce qui alourdit la taille du fichier.
• Les données reçues au format JSON ont l’avantage d’être très concises, mais ne sont
pas toujours très lisibles pour un humain. Un autre avantage est que les données sont
accessibles en tant qu’objets littéraux.
28 XMLHttpRequest
L’objet XMLHttpRequest
Présentation
L’objet XMLHttpRequest a été initialement conçu par Microsoft et implémenté dans
Internet Explorer et Outlook sous forme d’un contrôle ActiveX
(http://en.wikipedia.org/wiki/ActiveX). Nommé à l’origine XMLHTTP par Microsoft, il a
été par la suite repris par de nombreux navigateurs sous le nom que nous lui connaissons
actuellement : XMLHttpRequest. Sa standardisation viendra par la suite par le biais du
W3C.
Le principe même de cet objet est classique : une requête HTTP est envoyée à l’adresse
spécifiée, une réponse est alors attendue en retour de la part du serveur. Une fois la
réponse obtenue, la requête s’arrête et peut éventuellement être relancée.
XMLHttpRequest, versions 1 et 2
L’objet que nous allons étudier dans ce chapitre possède deux versions majeures. La
première version est celle issue de la standardisation de l’objet d’origine et son support
est assuré par tous les navigateurs. L’utilisation de cette première version est
extrêmement courante, mais les fonctionnalités paraissent maintenant bien limitées étant
donné l’évolution des technologies.
La seconde version introduit de nouvelles fonctionnalités intéressantes, comme la
gestion du cross-domain (nous reviendrons sur ce terme plus tard), ainsi que
l’introduction de l’objet FormData. Comment choisir la bonne version ? Les versions
d’Internet Explorer antérieures à la version 11 ne supportenent pas ou peu la version 2.
Si vous pouvez ignorer les vieux navigateurs, utilisez la version 2.
Vous avez sûrement pu constater que nous avons abrégé XMLHttpRequest par
XHR. Il s’agit d’une abréviation courante pour tout développeur JavaScript,
ne soyez donc pas étonnés de la voir sur de nombreux sites.
Cette ligne de code prépare une requête pour qu’elle contacte la page ajax.php sur le
nom de domaine mon_site_web.com par le biais du protocole http (vous pouvez très
bien utiliser d’autres protocoles, comme HTTPS ou FTP). Tout paramètre spécifié à la
requête sera transmis par le biais de la méthode GET.
Après préparation de la requête, il ne reste plus qu’à l’envoyer avec la méthode
send(). Cette dernière prend en paramètre un argument obligatoire que nous étudierons
plus tard. Dans l’immédiat, nous lui spécifions la valeur null :
xhr.send(null);
Si vous travaillez avec des requêtes asynchrones (ce que vous ferez dans
99 % des cas), sachez qu’il existe une méthode abort() permettant de
stopper toute activité. La connexion au serveur est alors interrompue et votre
instance de l’objet xhr est remise à zéro. Son utilisation est très rare, mais elle
peut servir si vous avez des requêtes qui prennent trop de temps.
Synchrone ou asynchrone ?
Vous connaissez probablement la signification de ces termes dans la vie courante, mais
que peuvent-ils donc désigner ici ? Une requête synchrone va bloquer votre script tant
que la réponse n’aura pas été obtenue, tandis qu’une requête asynchrone laissera
continuer l’exécution de votre script et vous préviendra de l’obtention de la réponse par
le biais d’un événement.
Quelle est la solution la plus intéressante ? Il s’agit sans conteste de la requête
asynchrone. Il est bien rare que vous ayez besoin que votre script reste inactif
simplement parce qu’il attend une réponse à une requête. La requête asynchrone vous
permet de gérer votre interface pendant que vous attendez la réponse du serveur, vous
pouvez donc indiquer au client de patienter ou vous occuper d’autres tâches en
attendant.
Il est conseillé, quelle que soit la méthode utilisée (GET ou POST), d’encoder toutes les
valeurs que vous passez en paramètres grâce à la fonction encodeURIComponent(),
afin d’éviter d’écrire d’éventuels caractères interdits dans une URL :
var value1 = encodeURIComponent(value1),
value2 = encodeURIComponent(value2);
Votre requête est maintenant prête à envoyer des paramètres par le biais de la méthode
GET.
En ce qui concerne la méthode POST, les paramètres ne sont pas à spécifier avec la
méthode open() mais avec la méthode send() :
xhr.open('POST', 'http://mon_site_web.com/ajax.php');
xhr.send('param1=' + value1 + '¶m2=' + value2);
Cependant, cet événement ne se déclenche pas seulement lorsque la requête est terminée
mais plutôt, comme son nom l’indique, à chaque changement d’état. Il existe cinq états
différents représentés par des constantes spécifiques à l’objet XMLHttpRequest :
De cette manière, notre code ne s’exécutera que lorsque la requête aura terminé son
travail. Cela ne veut pas forcément dire qu’elle l’a mené à bien. Pour nous en assurer,
nous allons devoir consulter le statut de la requête grâce à la propriété status. Cette
dernière renvoie le code correspondant à son statut, comme le fameux 404 pour les
fichiers non trouvés. Le statut qui nous intéresse est le statut 200, qui signifie que tout
s’est bien passé :
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
// Votre code…
}
});
À noter qu’il existe aussi une propriété nommée statusText contenant une version au
format texte du statut de la requête, en anglais seulement. Par exemple, un statut 404
vous donnera le texte suivant : « Not Found ».
Si vous souhaitez tester votre requête xhr sur votre ordinateur sans même
utiliser de serveur de test (WampServer, par exemple), vous n’obtiendrez
jamais de statut équivalent à 200 puisque c’est normalement le rôle du serveur
HTTP (Apache par exemple, fourni avec WampServer) de fournir cette valeur.
Vérifiez alors si le statut équivaut à 0, cela suffira.
Nous avons ici traité le cas d’une requête asynchrone, mais sachez que pour une requête
synchrone, il suffit de vérifier le statut de votre requête, tout simplement.
<line>
<cel>Ligne 1 - Colonne 1</cel>
<cel>Ligne 1 - Colonne 2</cel>
<cel>Ligne 1 - Colonne 3</cel>
</line>
<line>
<cel>Ligne 2 - Colonne 1</cel>
<cel>Ligne 2 - Colonne 2</cel>
<cel>Ligne 2 - Colonne 3</cel>
</line>
<line>
<cel>Ligne 3 - Colonne 1</cel>
<cel>Ligne 3 - Colonne 2</cel>
<cel>Ligne 3 - Colonne 3</cel>
</line>
</table>
alert(xhr.getResponseHeader('Content-type')); // Affiche :
// « text/html; charset=utf-8 »
Mise en pratique
La présentation de cet objet étant assez segmentée, nous n’avons pas encore eu
l’occasion d’étudier un quelconque exemple. Nous allons donc créer une page qui va
charger le contenu de deux autres fichiers selon le choix de l’utilisateur.
Commençons par le plus simple et créons notre page HTML qui va récupérer le contenu
des deux fichiers :
<p>
Veuillez choisir quel est le fichier dont vous souhaitez voir le contenu :
</p>
<p>
<input type="button" value="file1.txt" />
<input type="button" value="file2.txt" />
</p>
<p id="fileContent">
<span>Aucun fichier chargé</span>
</p>
Comme vous pouvez le constater, le principe est très simple. Nous allons pouvoir
passer au code JavaScript. Créons tout d’abord une fonction qui sera appelée lorsque
l’utilisateur cliquera sur l’un des deux boutons. Elle sera chargée de s’occuper du
téléchargement et de l’affichage du fichier passé en paramètre :
function loadFile(file) {
xhr.addEventListener('readystatechange', function() {
// On gère ici une requête asynchrone
document.getElementById('fileContent').innerHTML = '<span>' +
xhr.responseText + '</span>'; // Et on affiche !
});
Il ne nous reste plus qu’à mettre en place les événements qui déclencheront tout le
processus.
(function() { // Comme d'habitude, une IIFE pour éviter les variables globales
inputs[i].addEventListener('click', function() {
loadFile(this.value); // À chaque clique, un fichier sera
// chargé dans la page
});
})();
Mais attention, évitez de laisser cette condition lorsque votre script sera sur le serveur,
car la valeur 0 est une valeur d’erreur. Autrement dit, si une fois en ligne votre requête
rencontre un problème, 0 sera peut-être également retourné. Je dis peut-être, car 0 n’est
pas une valeur autorisée comme code HTTP. C’est toutefois documenté par le W3C
comme étant une valeur retournée dans certains cas, mais c’est un peu complexe.
xhr.addEventListener('readystatechange', function() {
// On gère ici une requête asynchrone
document.getElementById('fileContent').innerHTML = '<span>' +
xhr.responseText + '</span>'; // On l'affiche !
});
Il existe donc beaucoup de manières de faire pour pas grand-chose, un bon paramétrage
du serveur HTTP (Apache dans notre cas) est généralement suffisant, à condition
d’avoir des fichiers encodés avec la norme spécifiée par le serveur, bien sûr.
Nous vous avons présenté ces différentes manières car vous risquez de rencontrer des
problèmes d’encodage avec Ajax et ce petit récapitulatif pourra sûrement vous aider à
les résoudre.
Le problème
Le problème est de taille, mais vous verrez qu’il peut aisément être résolu une fois le
concept intégré. Le problème est donc le suivant : lorsque vous effectuez une requête
Ajax, toutes les données sont envoyées avec un encodage UTF-8, quel que soit
l’encodage du fichier HTML qui contient le script pour la requête Ajax.
Ceci pose problème si vous travaillez avec un autre encodage que l’UTF-8 côté serveur.
En effet, si le fichier PHP appelé par la requête Ajax est encodé, par exemple, en ISO
8859-1, alors il se doit de travailler avec des données ayant le même encodage, ce que
ne fournira pas une requête Ajax.
Dans le cas contraire, vous allez vous retrouver avec des caractères étranges en lieu et
place de certains caractères situés dans le texte d’origine, tout particulièrement pour les
caractères accentués.
Comme dit précédemment, l’ISO 8859-1 n’utilise que 8 bits pour l’encodage des
caractères, tandis que l’UTF-8 peut aller jusqu’à 32 bits. À première vue, ces deux
normes ne se ressemblent pas, et pourtant si ! Leurs 7 premiers bits respectifs assignent
les mêmes valeurs aux caractères concernés. Ainsi la lettre A est représentée par ces 7
bits quelle que soit la norme utilisée (ISO ou l’UTF-8) : 100 0001.
La différence se situe en fait pour les caractères que nous allons qualifier
« d’exotiques », comme les caractères accentués. Ainsi, un e avec accent circonflexe (ê)
a la valeur binaire suivante en ISO 8859-1 : 1110 1010, ce qui en UTF-8 équivaut à un
caractère impossible à afficher.
Mais les choses se corsent encore plus lorsque la conversion est faite depuis l’UTF-8
vers une autre norme, comme l’ISO 8859-1 car l’UTF-8 utilisera parfois 2 octets (voire
plus) pour stocker un seul caractère, ce que les autres normes interpréteront comme
étant deux caractères. Par exemple, la même lettre ê encodée en UTF-8 donne le code
binaire suivant : 1100 0011 1010 1010. L’ISO 8859-1 va y voir 2 octets puisqu’il y a 16
bits : la première séquence de 8 bits (1100 0011) va donc être traduite par le caractère
Ã, et la seconde séquence (1010 1010) par le caractère ª.
Cela signifie que si votre fichier HTML client est en ISO 8859-1 et qu’il envoie par
Ajax le caractère ê à une page PHP encodée elle aussi en ISO 8859-1, les données qui
seront lues par le serveur seront les suivantes : ê.
Deux solutions
Il existe deux solutions pour éviter ce problème d’encodage sur vos requêtes Ajax.
La première, qui est de loin la plus simple et la plus pérenne, consiste à encoder votre
site entièrement en UTF-8. Ainsi, les requêtes Ajax envoient des données en UTF-8 qui
seront reçues par un serveur demandant à traiter de l’UTF-8, donc sans aucun problème.
Un site en UTF-8 implique que tous vos fichiers texte soient encodés en UTF-8, que le
serveur indique au client le bon encodage, et que vos ressources externes, comme les
bases de données, soient aussi en UTF-8.
Cette solution est vraiment la meilleure, mais elle est difficile à mettre en place sur un
projet web déjà bien avancé. Si vous souhaitez vous y mettre (et c’est fortement
conseillé), vous pouvez lire le cours intitulé « Passer du latin1 à l’unicode »
(https://openclassrooms.com/courses/passer-du-latin1-a-l-unicode) écrit par vyk12
(https://openclassrooms.com/membres/vyk12-68874)..
La seconde solution, encore bien souvent rencontrée, est plus adaptée si votre projet est
déjà bien avancé et que vous ne pouvez pas faire une conversion complète de son
encodage. Il s’agit de décoder les caractères reçus par le biais d’une requête Ajax avec
la fonction PHP utf8_decode().
Admettons que vous envoyiez une requête Ajax à la page suivante :
<?php
echo $_GET['parameter'];
?>
La solution consiste donc à décoder l’UTF-8 reçu pour le convertir en ISO 8859-1, la
fonction utf8_decode() intervient donc ici :
<?php
echo utf8_decode($_GET['parameter']);
?>
Ce qui donne :
Drôle de tête
Lorsque vous renvoyez les données du serveur au client, il est inutile de les encoder en
UTF-8 car Ajax applique une conversion UTF-8 uniquement à l’envoi des données,
comme vu précédemment. Donc si vous affichez des données en ISO 8859-1, elles
arriveront chez le client avec le même encodage.
Cette sécurité est certes impérative, mais il peut arriver parfois que nous possédions
deux sites web dont les noms de domaines sont différents, mais dont la connexion doit
se faire par le biais des requêtes xhr. La seconde version du xhr introduit donc un
système simple et efficace permettant l’autorisation des requêtes cross-domain.
Ensuite, il ne vous reste plus qu’à ajouter cet en-tête aux autres en-têtes de votre page
web, comme ici en PHP :
<?php
header('Access-Control-Allow-Origin: *');
?>
Cependant, n’utilisez l’astérisque qu’en dernier recours, car lorsque vous autorisez un
nom de domaine à effectuer des requêtes cross-domain sur votre page, cela revient à
désactiver une sécurité contre le piratage de ce domaine.
Il est possible que le support de cette propriété soit partiel, car elle fut
longtemps absente de tous les navigateurs. Testez et renseignez-vous avant
d’utiliser cette propriété.
xhr.open('GET', 'http://example.com');
xhr.overrideMimeType('text/xml');
?>
Cependant, cette facilité d’utilisation est loin d’être présente lorsque vous souhaitez
accéder à ces ressources avec une requête cross-domain, car aucune valeur ne sera
retournée par les tableaux $_COOKIE et $_SESSION.
En effet, les cookies et les sessions ne sont pas envoyés. Il ne s’agit pas d’une
fonctionnalité conçue pour vous embêter, mais bien d’une sécurité, car vous allez devoir
autoriser le navigateur et le serveur à gérer ces données.
Quand nous parlons du serveur, nous voulons surtout parler de la page appelée par la
requête. Vous allez devoir y spécifier l’en-tête suivant pour autoriser l’envoi des
cookies et des sessions :
Access-Control-Allow-Credentials: true
Mais, côté serveur, cela ne suffira pas si vous avez spécifié l’astérisque * pour l’en-tête
Access-Control-Allow-Origin. Il vous faut absolument spécifier un seul nom de
domaine, ce qui est malheureusement très contraignant dans certains cas d’applications
(bien qu’ils soient rares).
Vous devriez maintenant avoir une page PHP commençant par un code de ce type :
<?php
header('Access-Control-Allow-Origin: http://example.com');
header('Access-Control-Allow-Credentials: true');
?>
Cependant, vous pourrez toujours tenter d’accéder aux cookies ou aux sessions, vous
obtiendrez en permanence des valeurs nulles. La raison est simple : le serveur est
configuré pour permettre l’accès à ces données, mais le navigateur ne les envoie pas.
Pour pallier ce problème, il suffit d’indiquer à notre requête que l’envoi de ces données
est nécessaire. Cela se fait après initialisation de la requête et avant son envoi
(autrement dit, entre l’utilisation des méthodes open() et send()) avec la propriété
withCredentials :
xhr.open( … );
xhr.send( … );
Une question technique maintenant : imaginons une page web nommée client.php
située sur un nom de domaine A. Depuis cette page, nous appelons server.php située
sur le domaine B grâce à une requête cross-domain. Les cookies et les sessions reçus
par la page server.php sont-ils ceux du domaine A ou ceux du domaine B ?
La réponse est simple et logique : il s’agit de ceux du domaine B. Si vous effectuez une
requête cross-domain, les cookies et les sessions envoyés seront constamment ceux qui
concernent le domaine de la page appelée. Cela s’applique aussi si vous utilisez la
fonction PHP setcookie() dans la page appelée : les cookies modifiés seront ceux du
domaine de cette page, et non pas ceux du domaine d’où provient la requête.
Une dernière précision, rappelez-vous bien que tout ce qui a été étudié ne vous
concerne que lorsque vous effectuez une requête cross-domain. Dans le cas
d’une requête dite « classique », vous n’avez pas à faire ces manipulations,
tout fonctionne sans cela, même pour une requête xhr1.
Les deux événements suivants sont error et abort. Le premier se déclenche en cas de
non-aboutissement de la requête (quand readyState n’atteint même pas la valeur
finale : 4), tandis que le second s’exécutera en cas d’abandon de la requête avec la
méthode abort() ou avec le bouton Arrêt de l’interface du navigateur web.
Vous souvenez-vous de la propriété timeout ? Sachez qu’il existe un événement du
même nom qui se déclenche quand la durée maximale spécifiée dans la propriété
associée est atteinte.
});
Au final, l’utilité de cet événement est assez quelconque, ce dernier a bien plus d’intérêt
dans le cas d’un upload (cela sera abordé dans la partie consacrée au HTML 5). Cela
dit, il peut avoir son utilité dans le cas de préchargements de fichiers assez lourds.
Ainsi, le préchargement de plusieurs images avec une barre de progression peut être une
utilisation qui peut commencer à avoir son intérêt (mais, nous vous l’accordons, cela
n’a rien de transcendant).
Cet événement n’étant pas très important, nous ne présenterons pas un exercice détaillé.
Toutefois, vous trouverez le lien suivant vers un exemple en ligne dont le code est
commenté, n’hésitez pas à y jeter un coup d’œil.
(Essayez une adaptation de cet événement : https://user.oc-
static.com/ftp/javascript/part4/chap2/ex3.html)
L’objet FormData
Cet objet consiste à faciliter l’envoi des données par le biais de la méthode POST des
requêtes xhr. Comme dit précédemment, l’envoi des données par le biais de POST est
une chose assez fastidieuse, car il faut spécifier un en-tête dont on ne se souvient que
très rarement, et on perd alors du temps à le chercher sur le Web.
Au-delà de son côté pratique en termes de rapidité d’utilisation, l’objet FormData est
aussi un formidable outil permettant d’envoyer des données binaires au serveur. Ceci
signifie qu’il est possible de charger des fichiers par le biais des requêtes xhr.
Cependant, l’upload de fichiers nécessite des connaissances approfondies sur le HTML
5, comme nous le verrons dans le chapitre qui lui est consacré. Nous allons tout d’abord
nous contenter d’une utilisation relativement simple.
Pour commencer, l’objet FormData doit être instancié :
var form = new FormData();
Vous pouvez ensuite vous servir de son unique méthode : append(), qui ne retourne
aucune valeur et prend en paramètres deux arguments obligatoires : le nom d’un champ
(qui correspond à l’attribut name des éléments d’un formulaire) et sa valeur. Son
utilisation est donc très simple :
form.append('champ1', 'valeur1');
form.append('champ2', 'valeur2');
Cet objet est intéressant car il est inutile de spécifier un en-tête particulier pour indiquer
que nous envoyons des données sous forme de formulaire. Il suffit de passer l’objet de
type FormData à la méthode send(), ce qui donne ceci sur un code complet :
var xhr = new XMLHttpRequest();
xhr.open('POST', 'script.php');
xhr.send(form);
Côté serveur, vous pouvez récupérer les données tout aussi simplement que vous le
faites d’habitude :
<?php
?>
Revenons rapidement sur le constructeur de cet objet, car il possède un argument bien
pratique : passez donc en paramètre un élément de formulaire et votre objet FormData
sera alors prérempli avec toutes les valeurs de votre formulaire. Voici un exemple
simple :
<form id="myForm">
<input id="myText" name="myText" type="text" value="Test ! Un, deux, un, deux !"
/>
</form>
<script>
xhr.open('POST', 'script.php');
xhr.send(form);
</script>
Cet objet est donc bien pratique, même si vous ne savez pas encore uploader des
fichiers car il facilite les choses !
En résumé
• L’objet XMLHttpRequest est l’objet le plus utilisé pour l’Ajax. Deux versions de cet
objet existent, la seconde étant plus complète mais pas toujours disponible au sein de
tous les navigateurs.
• Deux modes sont disponibles : synchrone et asynchrone. Une requête de mode
asynchrone sera exécutée en parallèle et ne bloquera pas l’exécution du script, tandis
que la requête synchrone attendra la fin de la requête pour poursuivre l’exécution du
script.
• Deux méthodes d’envoi sont utilisables : GET et POST. Dans le cas d’une méthode
GET, les paramètres de l’URL doivent être encodés avec encodeURIComponent().
• Il faut faire attention à l’encodage, car toutes les requêtes sont envoyées en UTF-8.
• La version 2 du xhr introduit les requêtes cross-domain ainsi que les objets
FormData et de nouveaux événements.
29 Upload via une iframe
Accéder au contenu
Pour accéder au contenu de l’iframe, il faut tout d’abord accéder à l’iframe elle-même,
puis passer par la propriété contentDocument :
var frame = document.getElementById('myFrame').contentDocument
Une fois que nous avons accédé au contenu de l’iframe, c’est-à-dire à son document,
nous pouvons naviguer dans le DOM comme s’il s’agissait d’un document « normal » :
var frame_links = frame.getElementsByTagName('a').length;
La règle de sécurité same origin policy s’applique aussi aux iframes, ce qui
signifie que si vous êtes sur une page d’un domaine A et que vous appelez une
page d’un domaine B par le biais d’une iframe, vous ne pourrez pas accéder
au contenu de la page B depuis la page A.
Chargement de contenu
Il existe deux techniques pour charger une page dans une iframe : la première consiste à
changer l’attribut src de l’iframe via le JavaScript, la seconde consiste à ouvrir un lien
dans l’iframe. Cette action est rendue possible via l’attribut target (standardisé en
HTML 5) que nous pouvons utiliser sur un lien ou sur un formulaire. C’est cette dernière
technique que nous utiliserons pour la réalisation du système d’upload.
L’attribut target indique au formulaire que son contenu doit être envoyé au sein de
l’iframe dont l’attribut name est myFrame (l’attribut name est donc obligatoire ici). De
cette manière, le contenu du formulaire y sera envoyé et la page courante ne sera pas
rechargée.
Le JavaScript pourra être utilisé comme méthode alternative pour envoyer le formulaire.
Pour rappel, la méthode à utiliser pour cela est submit() :
document.getElementById('myForm').submit();
Détecter le chargement
Avec l’événement load
Les iframes possèdent un événement load, déclenché une fois que leur contenu est
chargé. À chaque contenu chargé, load est déclenché. C’est un moyen efficace pour
savoir si le document est chargé, et ainsi pouvoir le récupérer. Voici un petit exemple :
<iframe src="file.html" name="myFrame" id="myFrame" onload="trigger()"></iframe>
<script>
function trigger() {
var frame = document.getElementById('myFrame').contentDocument;
alert(frame.body.textContent);
}
</script>
L’objet window.top pointe vers la fenêtre « mère », ce qui nous permet ici d’atteindre
la page qui contient l’iframe.
Voici un exemple qui illustre ce mécanisme :
<iframe src="file.html" name="myFrame" id="myFrame"></iframe>
<script>
function trigger() {
var frame = document.getElementById('myFrame').contentDocument;
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Suspendisse molestie
suscipit arcu.</p>
Récupération du contenu
Le chargement de données via une iframe présente un avantage important : il est
possible de charger n’importe quel type de donnée (une page web complète, du texte
brut ou même du JavaScript, comme le format JSON).
Récupérer des données JavaScript
Si nous reprenons l’exemple précédent, avec le callback, il est possible de récupérer
facilement des données JavaScript, comme un objet. Dans ce cas, il suffit d’utiliser du
PHP pour construire un objet qui sera transmis en paramètre de la fonction de callback,
comme ceci :
<?php
$fakeArray = array('Sébastien', 'Laurence', 'Ludovic');
?>
<script>
window.top.window.trigger(['<?php echo implode("','", $fakeArray) ?>']);
</script>
Ici, un tableau JavaScript est construit via le PHP et envoyé à la fonction trigger() en
tant que paramètre.
Exemple complet
<form id="myForm" method="post" action="request.php" target="myFrame">
<div>
<label for="nick">Votre pseudo :</label>
<input type="text" id="nick" name="nick" />
<script>
function sendForm() {
var nick = document.getElementById("nick").value;
if (nick) { // Si c'est OK
document.getElementById("myForm").submit(); // On envoie le formulaire
}
}
function receiveData(data) {
alert('Votre pseudo est "' + data + '"');
}
</script>
Le système d’upload
Par le biais d’un formulaire et d’une iframe, créer un système d’upload est relativement
simple ! Les éléments <form> possèdent un attribut enctype qui doit absolument
contenir la valeur multipart/form-data. Pour faire simple, cette valeur indique que
le formulaire est prévu pour envoyer de grandes quantités de données (les fichiers sont
des données volumineuses).
Notre formulaire d’upload peut donc être écrit comme ceci :
<form id="uploadForm" enctype="multipart/form-data" action="upload.php"
target="uploadFrame" method="post">
<label for="uploadFile">Image :</label>
<input id="uploadFile" name="uploadFile" type="file" />
<br /><br />
<input id="uploadSubmit" type="submit" value="Upload !" />
</form>
Nous plaçons ensuite l’iframe, ainsi qu’un autre petit <div> que nous utiliserons pour
afficher le résultat de l’upload :
<div id="uploadInfos">
<div id="uploadStatus">Aucun upload en cours</div>
<iframe id="uploadFrame" name="uploadFrame"></iframe>
</div>
document.getElementById('uploadForm').addEventListener('submit', function() {
document.getElementById('uploadStatus').innerHTML = 'Loading...';
});
Quelques explications sont nécessaires. Dès que le formulaire est envoyé, la fonction
anonyme de l’événement submit est exécutée. Celle-ci va remplacer le texte du
<div>#uploadStatus pour indiquer que le chargement est en cours, car en fonction de
la taille du fichier à envoyer, l’attente peut être longue. L’argument error contiendra
soit « OK », soit une explication sur une erreur éventuelle. L’argument path contiendra
l’URL du fichier venant d’être uploadé. L’appel vers la fonction uploadEnd() sera fait
via l’iframe, comme nous le verrons plus loin.
$error = NULL;
$filename = NULL;
$filename = $_FILES['uploadFile']['name'];
$targetpath = getcwd() . '/' . $filename;
// On stocke le chemin où enregistrer le fichier
<script>
window.top.window.uploadEnd("<?php echo $error; ?>",
"<?php echo $filename; ?>");
</script>
Avec ce code, le fichier uploadé est analysé, puis enregistré sur le serveur. Si
vous souhaitez obtenir plus d’informations sur le fonctionnement de ce code
PHP, n’hésitez pas à consulter le tutoriel « Upload de fichiers par formulaire »
(https://openclassrooms.com/courses/upload-de-fichiers-par-formulaire) écrit
par DHKold (https://openclassrooms.com/membres/dhkold-23596) sur
OpenClassrooms.
Avec ce script tout simple, il est donc possible de mettre en place un upload de fichiers
sans « rechargement ». Il ne reste plus qu’à améliorer le système, notamment en
sécurisant le script PHP (détecter le type MIME du fichier pour autoriser uniquement les
images, par exemple) ou en arrangeant le code JavaScript pour afficher à la suite les
fichiers uploadés s’il y en a plusieurs…
Si vous souhaitez essayer ce script en ligne, une version en ligne est disponible mais
elle n’enregistre pas les fichiers sur le serveur. Cela implique donc que l’affichage de
l’image ne soit pas effectué. En revanche, vous êtes prévenus lorsque l’upload du
fichier est terminé, ce qui est l’objectif principal de notre script.
(Essayez la version « light » : http://learn.sdlm.be/js/part4/chap3/ex3.html)
En résumé
• L’utilisation d’une iframe est une technique Ajax assez répandue et facile à mettre en
œuvre pour réaliser un upload de fichiers compatible avec tous les navigateurs.
• Il suffit d’utiliser l’événement load sur une iframe pour savoir si la page qu’elle
contient vient d’être chargée. Il ne reste plus qu’à accéder à cette page et à récupérer
ce qui nous intéresse.
• Depuis une iframe, il faut utiliser window.top pour accéder à la page qui contient
l’iframe. C’est utile dans le cas d’un callback.
30 Dynamic Script Loading
Un concept simple
Avec le DOM, il est possible d’insérer n’importe quel élément HTML au sein d’une
page web, et cela vaut également pour un élément <script>. Il est donc possible de lier
et d’exécuter un fichier JavaScript après le chargement de la page :
window.addEventListener('load', function() {
document.body.appendChild(scriptElement);
});
Avec ce code, un nouvel élément <script> sera inséré dans la page une fois que cette
dernière aura été chargée. Mais s’il est possible de charger un fichier JavaScript à la
demande, pourquoi ne pas s’en servir pour charger des données et « faire de l’Ajax » ?
Un premier exemple
Nous allons commencer par quelque chose de très simple : dans une page HTML, nous
allons charger un fichier JavaScript qui exécutera une fonction. Cette fonction se trouve
dans la page HTML :
<script>
function sendDSL() {
var scriptElement = document.createElement('script');
scriptElement.src = 'dsl_script.js';
document.body.appendChild(scriptElement);
}
function receiveMessage(message) {
alert(message);
}
</script>
Décortiquons tout cela. Dès que nous cliquons sur le bouton, la fonction sendDSL()
charge le fichier JavaScript contenant un appel vers la fonction receiveMessage(),
tout en prenant soin de lui passer un message en paramètre. Ainsi, nous pouvons
récupérer du contenu via la fonction receiveMessage(). Évidemment, cet exemple
n’est pas très intéressant puisque nous savons à l’avance ce que le fichier JavaScript va
renvoyer. Nous allons alors créer le fichier JavaScript via du PHP.
receiveMessage(string);
Charger du JSON
Comme dans l’exemple précédent, nous allons utiliser une page PHP pour générer le
contenu du fichier JavaScript, et donc notre JSON. Les données JSON contiennent une
liste d’éditeurs et pour chacun une liste de programmes qu’ils éditent :
<?php
header("Content-type: text/javascript");
?>
receiveMessage(softwares);
Au niveau de la page HTML, pas de gros changements… Nous allons seulement coder
une meilleure fonction receiveMessage() de manière à afficher, dans une alerte, les
données issues du JSON. Nous utilisons une boucle for in pour parcourir le tableau
associatif, et une seconde boucle for imbriquée pour chaque tableau :
<script>
function sendDSL() {
var scriptElement = document.createElement('script');
scriptElement.src = 'dsl_script_json.php';
document.body.appendChild(scriptElement);
}
function receiveMessage(json) {
var tree = '',
nbItems, i;
for (node in json) {
tree += node + "\n";
nbItems = json[node].length;
alert(tree);
}
</script>
En résumé
• Il est possible de charger un fichier .js en ajoutant un élément <script> via le
JavaScript. On appelle cela le Dynamic Script Loading.
• Cette technique est particulièrement efficace pour charger des données au format
JSON.
• Comme pour les iframes vues précédemment, il faut utiliser un système de callback
pour transmettre les données une fois le fichier JavaScript chargé.
31 Déboguer le code
Tout comme pour le DOM, il est tout à fait possible de déboguer vos requêtes grâce aux
kits de développement. Ce chapitre vous montre comment analyser les données
envoyées et reçues par le biais de vos requêtes, vérifier les paramètres utilisés pour
chacune d’entre elles et consulter leurs temps d’exécution.
La plupart du temps, votre phase de débogage concernera avant tout une ou plusieurs
requêtes xhr, mais rarement les autres requêtes. Afin de mieux vous y retrouver dans
une liste relativement longue, il est possible d’appliquer un filtre. Sous Chrome, cliquez
sur le bouton situé en haut à gauche et choisissez un type de requête (par exemple xhr).
Sous Firefox, vous trouverez tout simplement la liste des filtres en bas à gauche .
L’onglet Headers sous Chrome, ou En-têtes sous Firefox, permet de vérifier les
headers d’une requête mais aussi de la réponse, ce qui peut être extrêmement utile dans
le cas d’un bogue lié à la configuration des headers, tel que la mise en cache. Il est aussi
possible d’y consulter les cookies ou les paramètres de la requête (les paramètres dans
une URL ou bien les données d’un formulaire). À noter que sous Firefox, cette
consultation se fait dans les onglets Paramètres et Cookies.
Le panneau que vous utiliserez le plus souvent sera celui permettant d’obtenir les
données d’une réponse à une requête. Il s’agit de l’onglet Response sous Chrome et
Réponse sous Firefox. Dans le cas de données JSON, elles seront présentées sous
forme d’arborescence avec Firefox. Cet affichage est aussi disponible avec Chrome
mais il faut pour cela se rendre dans l’onglet Preview.
Enfin, il est possible d’analyser le temps d’exécution d’une requête, ce qui peut-être
particulièrement utile en cas de lenteurs. Si, par exemple, votre page web met du temps
à charger pour une raison inconnue, ne blâmez pas immédiatement votre site web, la
cause vient peut-être d’ailleurs. Par exemple, si vous chargez un script externe à votre
site, tel que Google Analytics, la lenteur vient peut-être de là.
Pour vérifier ce point, ouvrez l’onglet Timing sous Chrome, Délais sous Firefox. Les
valeurs qui vous intéressent avant tout sont Sending/Envoi, Waiting/Attente et
Receiving/Réception, qui correspondent respectivement :
• au temps d’envoi de la requête ;
• au temps d’attente entre la fin de l’envoi de la requête et le début de l’envoi de la
réponse, ce qui définit donc le temps de traitement du serveur ;
• au temps de réception de la réponse.
32 TP : un système
d’autocomplétion
Il est temps à présent de mettre en pratique une bonne partie des connaissances acquises
au cours des chapitres précédents. Ce TP sera consacré à la création d’un système
d’autocomplétion capable d’aller chercher dans un fichier les villes de France
commençant par les lettres que vous aurez commencé à écrire dans le champ de
recherche. Le but ici est d’accélérer et de valider la saisie des mots-clés.
Ce TP n’utilisera Ajax que par le biais de l’objet XMLHttpRequest afin de rester dans
des dimensions raisonnables. Cependant, il s’agit, et de loin, de la méthode la plus en
vogue de nos jours, l’utilisation d’iframes et de DSL étant réservée à des cas bien plus
particuliers.
Présentation de l’exercice
Les technologies à employer
Avant de commencer, nous devons déterminer le type de technologie dont nous avons
besoin, car nous allons ici employer le JavaScript avec d’autres langages.
Généralement, un système d’autocomplétion fait appel à trois technologies différentes :
• un langage client ayant la capacité de dialoguer avec un serveur ;
• un langage serveur capable de fournir les données au client ;
• une base de données qui stocke toutes les données.
Dans notre cas, nous allons utiliser le JavaScript ainsi que le PHP (bien que n’importe
quel autre langage serveur puisse le remplacer). Nous allons faire une petite entorse au
troisième point en utilisant un fichier de stockage plutôt qu’une base de données, cela
pour une raison bien simple : simplifier notre code, surtout que nous n’avons pas besoin
d’une base de données pour le peu de données à enregistrer.
Principe de l’autocomplétion
Un système d’autocomplétion se présente de la manière suivante.
Google a mis en place un système d’autocomplétion sur son moteur de recherche.
Le principe est simple mais efficace : dès qu’un utilisateur tape un caractère dans le
champ, une recherche est immédiatement effectuée et retournée au navigateur. Ce dernier
affiche alors les résultats dans un petit cadre, généralement situé sous le champ de
recherche. Les résultats affichés peuvent alors être parcourus à l’aide des touches
fléchées du clavier (haut et bas) ou de la souris. Lorsque nous choisissons l’un des
résultats listés, celui-ci apparaît automatiquement dans le champ de recherche à la place
de ce qui avait été saisi par l’utilisateur. Il ne reste plus qu’à lancer la recherche.
L’avantage de ce type de script est qu’il permet de faire gagner beaucoup de temps : la
recherche peut être effectuée en tapant seulement quelques caractères. Cela est aussi très
utile lorsqu’une partie seulement du terme recherché est connue ou en cas de faute de
frappe.
Conception
Nous connaissons à présent le principe de l’autocomplétion et les technologies
nécessaires. Cependant, nous ne savons toujours pas comment tout cela doit être utilisé.
Nous allons donc vous guider pour vous permettre de vous lancer sans trop
d’appréhension dans ce vaste projet.
L’interface
L’autocomplétion étant généralement affiliée à tout ce qui est du domaine de la
recherche, nous aurons besoin d’un champ de texte pour écrire les mots-clés.
Cependant, ce dernier va nous poser problème, car le navigateur enregistre
habituellement ce qui a été saisi dans les champs de texte afin de vous le proposer plus
tard sous forme d’autocomplétion. Cela sera redondant avec notre système…
Heureusement, il est possible de désactiver cette autocomplétion en utilisant l’attribut
autocomplete de cette manière :
À cela, nous allons devoir ajouter un élément capable d’englober les suggestions de
recherche. Celui-ci sera composé d’une balise <div> contenant autant de <div> que de
résultats, comme ceci :
<div id="results">
<div>Résultat 1</div>
<div>Résultat 2</div>
</div>
Chaque résultat dans les suggestions devra changer d’aspect lorsque celui-ci sera
survolé ou sélectionné à l’aide des touches fléchées.
En ce qui concerne un éventuel bouton de type Submit, nous allons nous en passer, car
notre but n’est pas de lancer la recherche, mais seulement d’afficher une
autocomplétion.
À titre d’information, puisque vous allez devoir gérer les touches fléchées,
voici les valeurs respectives de la propriété keyCode pour les touches Haut,
Bas et Entrée : 38, 40 et 13.
La communication client/serveur
Cette partie sera assez simple car nous allons simplement effectuer une requête à chaque
caractère écrit afin de proposer une liste de suggestions. Il nous faudra donc une
fonction liée à l’événement keyup de notre champ de recherche, qui permettra
d’effectuer une nouvelle requête à chaque caractère tapé.
Cependant, admettons que nous tapions deux caractères dans le champ de recherche, que
la première requête réponde en 100 ms et la deuxième en 65 ms : nous allons alors
obtenir les résultats de la première requête après ceux de la deuxième et donc afficher
des suggestions qui ne seront plus du tout en accord avec les caractères saisis dans le
champ de recherche. La solution à cela est simple : il suffit d’utiliser la méthode
abort() sur la première requête si celle-ci n’est pas terminée. Ainsi, elle ne risque pas
de renvoyer des informations obsolètes par la suite.
Le PHP n’étant peut-être pas votre point fort, nous allons détailler suffisamment les
différentes étapes pour que vous puissiez mener à bien votre recherche.
Tout d’abord, vous devez récupérer les données contenues dans le fichier towns.txt.
Pour cela, vous allez lire ce fichier avec la fonction file_get_contents()
(http://fr.php.net/file_get_contents), puis convertir son contenu en tant que tableau
PHP, grâce à la fonction unserialize() (http://fr2.php.net/unserialize).
Après avoir fait cela, le tableau obtenu doit être passé au filtre de la recherche de
résultats en cohérence avec les caractères saisis par l’utilisateur dans le champ de
recherche. Pour cela, vous aurez besoin d’une boucle ainsi que de la fonction count()
(http://fr.php.net/count) pour obtenir le nombre d’éléments contenus dans le tableau.
Pour vérifier si un index du tableau correspond à votre recherche, vous utiliserez la
fonction stripos() (http://fr.php.net/stripos), qui permet de vérifier si une chaîne de
caractères contient certains caractères, sans tenir compte de la casse. Si vous trouvez un
résultat en cohérence avec la recherche, il sera ajouté à un tableau (que vous aurez
préalablement créé) grâce à la fonction array_push() (http://fr.php.net/array_push).
Une fois le tableau parcouru, il ne vous reste plus qu’à trier les résultats avec la
fonction sort() (http://fr2.php.net/sort), puis à renvoyer les données au client sous
sous forme de texte brut, et non aux formats XML ou JSON. Les formats XML et JSON
sont utiles pour renvoyer des données qui ont besoin d’être structurées. Si nous avions
eu besoin de renvoyer, en plus des noms de ville, le nombre d’habitants, de commerces
et d’administrations françaises, alors nous aurions pu envisager l’utilisation du XML ou
du JSON afin de structurer les données. Mais dans notre cas, cela n’est pas utile car
nous ne faisons que renvoyer le nom de chaque ville trouvée.
Comment renvoyer les données au client sous forme de texte brut ? Nous pourrions faire
un saut de ligne entre chaque ville retournée, mais ce n’est pas très pratique à analyser
pour le JavaScript. Il faut donc choisir un caractère de séparation qui n’est jamais
utilisé dans le nom d’une ville. Dans ce TP, nous utiliserons la barre verticale |, ce qui
devrait nous permettre de retourner du texte brut sous cette forme :
Paris|Perpignan|Patelin-Paumé-Sur-Toise|Etc.
Tout comme join() en JavaScript, il existe une fonction PHP qui permet de concaténer
toutes les valeurs d’un tableau dans une chaîne de caractères avec un ou plusieurs
caractères de séparation : il s’agit de la fonction implode()
(http://fr2.php.net/implode). Une fois la fonction utilisée, il suffira de tout renvoyer au
client avec un simple echo sans oublier d’analyser cela côté JavaScript.
L’exemple ici étant relativement simple, nous nous permettons de nous passer
d’une structuration de données. Cependant, dans la plupart des situations,
vous utiliserez le JSON.
C’est à vous !
Maintenant que vous avez toutes les cartes en main, à vous de jouer ! N’hésitez pas à
consulter le corrigé du fichier PHP si besoin.
Mais alors, par où commencer ? Le serveur ou le client ? Il est préférable que vous
commenciez par le code PHP afin de vous assurer que celui-ci fonctionne correctement,
cela vous évitera bien des inconvénients de débogage.
En cas de dysfonctionnements dans votre code, consultez la console d’erreurs
(https://openclassrooms.com/courses/dynamisez-vos-sites-web-avec-javascript) et
vérifiez ce que vous a renvoyé le serveur, car l’erreur ne provient pas forcément du
client.
Corrigé
Que votre système d’autocomplétion soit fonctionnel ou non, il est important d’essayer
et de comprendre le cas échéant d’où proviennent les erreurs. Ce n’est pas très grave si
vous n’êtes pas allés jusqu’au bout.
Le corrigé complet
Vous trouverez ici le corrigé des différentes parties nécessaires à l’autocomplétion.
Commençons tout d’abord par le code PHP du serveur, car nous vous avions conseillé
de commencer par celui-ci :
<?php
$data = unserialize(file_get_contents('towns.txt'));
// Récupération de la liste complète des villes
$dataLen = count($data);
array_push($results, $data[$i]);
// On ajoute alors le résultat à la liste à retourner
}
}
?>
<body>
<div id="results"></div>
</body>
</html>
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState == 4 && xhr.status == 200) {
displayResults(xhr.responseText);
}
});
xhr.send(null);
return xhr;
if (response.length) {
// On ne modifie les résultats que si on en a obtenu
response = response.split('|');
var responseLen = response.length;
div = results.appendChild(document.createElement('div'));
div.innerHTML = response[i];
div.addEventListener('click', function(e) {
chooseResult(e.target);
});
function chooseResult(result) {
// Choisit un des résultats d'une requête et gère tout ce qui y est attaché
searchElement.addEventListener('keyup', function(e) {
divs[selectedResult--].className = '';
divs[++selectedResult].className = 'result_focus';
chooseResult(divs[selectedResult]);
previousValue = searchElement.value;
previousRequest = getResults(previousValue);
// On stocke la nouvelle requête
selectedResult = -1;
// On remet la sélection à "zéro" à chaque caractère écrit
});
})();
Les explications
Ce TP n’est pas compliqué en soi mais il aborde de nouveaux concepts. Il se peut donc
que vous soyez un peu perdus à la lecture des codes fournis. Laissez-nous vous
expliquer comment tout cela fonctionne.
Les codes qui vont suivre utilisent diverses fonctions que vous ne connaissez
peut-être pas. Si c’est le cas, n’hésitez pas à retourner au début de ce chapitre,
vous y trouverez de plus amples informations.
Avant de commencer notre analyse des données, il nous faut précharger le fichier,
convertir son contenu en tableau PHP et enfin trier ce dernier. À cela s’ajoutent le calcul
de la taille du tableau généré ainsi que la création d’un tableau pour sauvegarder les
résultats en cohérence avec la recherche :
<?php
$data = unserialize(file_get_contents('towns.txt'));
// Récupération de la liste complète des villes
$dataLen = count($data);
$results = array();
// Le tableau où seront stockés les résultats de la recherche
?>
Maintenant que toutes les données sont accessibles, nous devons les analyser. De façon
basique, il s’agit de la même opération qu’en JavaScript : une boucle pour parcourir le
tableau et une condition pour déterminer si le contenu est valide. Voici ce que cela
donne en PHP :
<?php
// La boucle ci-dessous parcourt tout le tableau $data, jusqu'à un maximum
// de 10 résultats
// Du code…
}
}
?>
La boucle for possède une condition un peu particulière qui stipule qu’elle doit
continuer à tourner tant qu’elle n’a pas lu tout le tableau $data et qu’elle n’a pas atteint
le nombre maximal de résultats à retourner. Cette limite de résultats est nécessaire, car
une autocomplétion ne doit pas afficher tous les résultats sous peine de provoquer des
ralentissements dus au nombre élevé de données, sans compter qu’un trop grand nombre
de résultats serait difficile à parcourir (et à analyser) pour l’utilisateur.
La fonction stripos() retourne la première occurrence de la recherche détectée dans
la valeur actuellement analysée. Il est nécessaire de vérifier que la valeur retournée est
bien égale à 0, car nous ne souhaitons obtenir que les résultats qui commencent par notre
recherche. La triple équivalence (===) s’explique par le fait que la fonction stripos()
retourne false en cas d’échec de la recherche, ce que la double équivalence (==) aurait
confondu avec un 0.
Une fois qu’un résultat cohérent a été trouvé, il ne reste plus qu’à l’ajouter à notre
tableau $results :
<?php
array_push($results, $data[$i]);
// On ajoute alors le résultat à la liste à retourner
}
}
?>
Une fois que la boucle a terminé son exécution, il ne reste plus qu’à retourner le contenu
de notre tableau de résultats sous forme de chaîne de caractères. Lors de la présentation
de ce TP, nous avons évoqué le fait de retourner les résultats séparés par une barre
verticale, c’est donc ce que nous appliquons dans le code suivant :
<?php
echo implode('|', $results);
// On affiche les résultats séparés par une barre verticale |
?>
Ainsi, notre script côté client n’aura plus qu’à faire un split('|') sur la chaîne de
caractères obtenue grâce au serveur pour avoir un tableau listant les résultats obtenus.
<div id="results"></div>
Voilà pour la partie HTML. Concernant le JavaScript, nous devons tout d’abord
déclarer les variables dont nous allons avoir besoin, avant de créer les événements.
Nous allons les déclarer dans une IIFE (pour un rappel sur les IIFE, consultez le
chapitre 6 sur les fonctions.
(function() {
})();
});
Nous allons nous intéresser à la gestion des touches fléchées Haut et Bas. Pour ce faire,
nous utiliserons la variable selectedResult qui stocke la position actuelle de la
sélection des résultats. La valeur -1 indique qu’il n’y a aucune sélection et le curseur se
trouve donc sur le champ de recherche ; la valeur 0 indique que le curseur est positionné
sur le premier résultat, la valeur 1 désigne le deuxième résultat, etc.
Pour chaque déplacement de la sélection, vous devez appliquer un style sur le résultat
sélectionné afin de le distinguer des autres. Il existe plusieurs solutions pour cela,
cependant nous avons retenu celle qui utilise les classes CSS. Autrement dit, lorsqu’un
résultat est sélectionné, vous n’avez qu’à lui attribuer une classe CSS qui modifiera son
style. Cette classe doit bien sûr être retirée dès qu’un autre résultat est sélectionné.
Cette solution donne ceci pour la gestion de la flèche Haut :
1. if (e.keyCode == 38 && selectedResult > -1) {
// Si la touche pressée est la flèche « haut »
2.
3. divs[selectedResult--].className = ''; // On retire la classe de l'élément
// inférieur et on décrémente la variable « selectedResult »
4.
5. if (selectedResult > -1) { // Cette condition évite une modification de
// childNodes[-1], qui n'existe pas
6. divs[selectedResult].className = 'result_focus';
// On applique une classe à l'élément actuellement sélectionné
7. }
8.
9. }
divs[++selectedResult].className = 'result_focus';
Ici, les changements portent surtout sur les valeurs à analyser ou à modifier. Nous ne
décrémentons plus selectedResult, nous l’incrémentons. La première condition est
modifiée afin de vérifier que nous ne nous trouvons pas à la fin des résultats au lieu du
début, etc.
Par ailleurs, une nouvelle ligne (la troisième) est ajoutée, elle permet d’afficher les
résultats dans tous les cas. L’utilisation de notre script sera ainsi simplifiée. Vous le
constaterez plus tard, mais lorsque vous choisirez un résultat (en cliquant dessus ou en
appuyant sur la touche Entrée), cela entraînera la disparition de la liste des résultats.
Grâce à l’ajout de notre ligne de code, vous pourrez les réafficher très simplement en
appuyant sur la flèche Bas !
Venons-en maintenant à la gestion de cette fameuse touche Entrée :
else if (e.keyCode == 13 && selectedResult > -1) {
// Si la touche pressée est « Entrée »
chooseResult(divs[selectedResult]);
La fonction chooseResult() est l’une des trois fonctions que nous allons créer plus
tard. Pour le moment, retenez seulement qu’elle permet de choisir un résultat (et donc de
gérer tout ce qui s’ensuit) et qu’elle prend en paramètre l’élément à choisir. Nous nous
intéresserons à son code ultérieurement.
Maintenant, il ne nous reste plus qu’à détecter quand le champ de texte a été modifié.
Nous pourrions penser que cela se produit à chaque fois que l’événement keyup se
déclenche, mais pas tout à fait en réalité. Cet événement se déclenche quelle que soit la
touche relâchée, et cela inclut les touches fléchées, les touches de fonctions, etc. Tout
cela nous pose problème car nous souhaitons savoir quand la valeur du champ de
recherche est modifiée et non pas quand une touche quelconque est relâchée. Une
solution serait alors de vérifier que la touche enfoncée fournit bien un caractère, mais il
s’agit d’une vérification assez fastidieuse et pas forcément simple à mettre en place
pour être compatible avec Internet Explorer.
Nous allons donc recourir à la variable previousValue, dans laquelle nous
enregistrerons la dernière valeur du champ de recherche. Ainsi, dès que notre
événement se déclenche, il suffit de comparer la variable previousValue à la valeur
actuelle du champ de recherche. Si elles sont différentes, nous enregistrons la nouvelle
valeur du champ dans la variable, nous poursuivons l’exécution de la suite de notre
programme et c’est reparti pour un tour. Simple, mais efficace !
Une fois que nous savons que la valeur de notre champ de texte a été modifiée, il ne
nous reste plus qu’à lancer une nouvelle requête effectuant la recherche auprès du
serveur :
else if (searchElement.value != previousValue) {
// Si le contenu du champ de recherche a changé
previousValue = searchElement.value;
// On change la valeur précédente par la valeur actuelle
La fonction getResults() est censée retourner l’objet xhr initialisé, nous profitons
donc de cela pour stocker ce dernier dans la variable previousRequest (ligne 9).
Ligne 5, vous pouvez voir une condition qui vérifie si la variable previousRequest est
bien initalisée et surtout si l’objet xhr qu’elle référence a bien terminé son travail. Si
l’objet existe mais que son travail n’est pas terminé, alors on utilise la méthode
abort() sur cet objet avant de faire une nouvelle requête.
}
});
xhr.send(null);
return xhr;
Nous avons donc une requête xhr banale qui envoie les termes de la recherche à la page
search.php, le tout dans une variable GET nommée s. N’oubliez pas d’utiliser la
fonction encodeURIComponent() afin d’éviter tout caractère indésirable dans l’URL de
la requête.
Le mot-clé return en fin de fonction retourne l’objet xhr initialisé afin qu’il puisse être
stocké dans la variable previousRequest pour effectuer une éventuelle annulation de
la requête grâce à la méthode abort().
Une fois la requête terminée et réussie, il ne reste plus qu’à afficher les résultats. Nous
allons donc passer ces derniers en paramètres à la fonction displayResults() :
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState == 4 && xhr.status == 200) {
displayResults(xhr.responseText);
}
});
if (response.length) {
// On ne modifie les résultats que si on en a obtenu
response = response.split('|');
// On parse la réponse de la requête pour obtenir les résultats dans un
tableau
var responseLen = response.length;
div = results.appendChild(document.createElement('div'));
// Ajout d'un nouvel élément <div>
div.innerHTML = response[i];
div.addEventListener('click', function(e) {
chooseResult(e.target);
});
Cette fonction crée un nouvel élément pour chaque résultat trouvé et lui attribue un
contenu et un événement.
Il ne nous reste plus qu’à étudier la fonction chooseResult(). Son but est évident :
choisir un résultat, ce qui signifie qu’un résultat a été sélectionné et doit venir remplacer
le contenu de notre champ de recherche. Du point de vue de l’utilisateur, l’opération
semble simple, mais du point de vue du développeur, il faut penser à gérer pas mal de
choses, comme la réinitialisation des styles des résultats. Voici la fonction :
function chooseResult(result) {
// Choisit un des résultats d'une requête et gère tout ce qui y est attaché
Rien de bien compliqué pour cette fonction, mais il fallait penser à tous ces petits
détails pour éviter d’éventuels bogues.
Le corrigé de ce TP est maintenant terminé, n’hésitez pas à l’améliorer selon vos envies,
les possibilités sont multiples.
Idées d’amélioration
Avant de passer au chapitre suivant, nous vous proposons deux idées d’amélioration.
La première est de faciliter la saisie des caractères dans le champ de texte. Il s’agit ici
d’écrire dans le champ le premier résultat et de surligner la partie qui n’a pas été
mentionnée par l’utilisateur. Voir par exemple la figure suivante.
Le premier résultat est écrit dans le champ et une partie est grisée.
Comme vous pouvez le constater, nous avons commencé à écrire les lettres « to ». Les
résultats sont apparus et surtout le script a rajouté les derniers caractères du premier
résultat tout en les surlignant afin que nous puissions réécrire par-dessus sans être gêné
dans notre saisie. Ce n’est pas très compliqué à mettre en place, mais cela vous
demandera un petit approfondissement du JavaScript, notamment grâce au cours
« Insertion de balises dans une zone de texte »
(https://openclassrooms.com/courses/insertion-de-balises-dans-une-zone-de-texte)
écrit par Sébastien de la Marck (https://openclassrooms.com/membres/thunderseb-
14765). Vous y apprendrez à surligner seulement une partie d’un texte contenu dans un
champ.
La seconde amélioration consiste à vous faire utiliser un format de structuration afin
d’afficher davantage de données. Par exemple, vous pouvez très bien ajouter des
données pour quelques villes (pas toutes quand même), tout transférer de manière
structurée grâce au JSON et afficher le tout dans la liste des résultats, comme ceci par
exemple :
Il est possible d’afficher des données sur les villes grâce au JSON.
Cela fait un bon petit défi, n’est-ce pas ? À vous de choisir si vous souhaitez vous
lancer dans cette aventure. Nous nous retrouvons à la prochaine partie, qui traitera du
HTML 5.
Cinquième partie
JavaScript et HTML 5
Cette partie va enfin aborder HTML 5. Vous découvrirez ici de nouvelles technologies
qui vous permettront d’augmenter les possibilités d’interactions entre l’utilisateur et
votre site web. Attendez-vous à voir germer de nombreuses idées dans votre cerveau,
car les nouvelles possibilités qui vont s’offrir à vous sont immenses.
Rappelons tout de même que HTML 5 est une technologie encore récente, si bien que
certains navigateurs ne le supportent pas, ou partiellement.
33 Qu’est-ce que le HTML 5 ?
Le logo du HTML 5
Accessibilité et sémantique
Le HTML 5 apporte dès lors de nouveaux éléments comme <nav>, <header>,
<article>, <figure>, etc., qui améliorent l’accessibilité, ainsi que des éléments
comme <mark> ou <data> qui améliorent la sémantique (c’est-à-dire le sens qu’on
donne aux textes).
Il est question d’API depuis le début de ce chapitre, mais cet acronyme est
peut être nouveau pour vous. API signifie Application Programming
Interface. Dans le cadre d’une utilisation en JavaScript, une API est un
ensemble d’objets, de méthodes et de propriétés réunis sous un même thème.
Si nous considérons l’API Drag & Drop, il s’agit d’un ensemble d’objets, de
propriétés, de méthodes et même d’attributs HTML qui permettent de réaliser
des glisser-déposer (drag and drop en anglais).
Web Storage
Le Web Storage est, d’une certaine manière, le successeur des fameux cookies. Cette
API permet de conserver des informations dans la mémoire du navigateur, pendant votre
navigation ou pour une durée beaucoup plus longue. Les cookies fournissent plus ou
moins 4 Ko de stockage, alors que le Web Storage en propose 5 Mo pour la plupart des
navigateurs et 10 Mo pour Internet Explorer. Cependant, le Web Storage n’est pas
accessible par les serveurs web, les cookies sont donc toujours de rigueur.
Pour enregistrer une valeur, il suffit d’écrire :
localStorage.setItem('nom-de-ma-cle', 'valeur de la clé');
Il faut donc donner un nom à la clé pour pouvoir récupérer la valeur plus tard :
alert(localStorage.getItem('nom-de-ma-cle'));
WebSocket
Le WebSocket permet à une page web de communiquer avec le serveur web de façon
bidirectionnelle, ce qui signifie que le serveur peut envoyer des informations à la page
et inversement. C’est en quelque sorte une API approfondie du XMLHttpRequest, plus
complexe, car cela nécessite un serveur adapté :
• HTML 5 et les WebSockets (http://blog.zenika.com/index.php?
post/2011/02/25/Html5-et-les-webSockets) ;
• HTML 5 – Les API JavaScript (http://blog.xebia.fr/2010/03/18/html5-les-api-
javascript/ #WebSocket) ;
• documentation MDN (https://developer.mozilla.org/en/WebSockets).
Geolocation
L’API de géolocalisation permet de détecter la position géographique du visiteur.
Attention, cela ne fonctionne que si l’utilisateur donne son accord, en réglant les
paramètres de navigation de son navigateur :
• tutoriel de géolocalisation en HTML 5 (http://www.html5-css3.fr/html5/tutoriel-
geolocalisation-html5) ;
• l’API géolocalisation en HTML 5 (http://debray-jerome.developpez.com/articles/l-
api-geolocalisation-en-html5/) ;
• HTML 5 – Les API JavaScript (http://blog.xebia.fr/2010/03/18/html5-les-api-
javascript/#Geolocation) ;
• documentation MDN (https://developer.mozilla.org/En/Using_geolocation).
Workers et Messaging
L’API Workers permet d’exécuter du code en tâche de fond. Ce code est alors exécuté
parallèlement à celui de la page. Si le code de la page rencontre une erreur, cela
n’affecte pas le code de l’API Workers et inversement.
L’API Workers peut envoyer des messages au script principal via l’API Messaging. Le
script principal peut envoyer des messages à l’API Workers. L’API Messaging peut
aussi être utilisée pour envoyer et recevoir des messages entre une <iframe> et sa page
mère, même si elles ne sont pas hébergées sur le même domaine.
En résumé
• HTML 5 est la nouvelle mouture du langage de balisage HTML. Il a été conçu afin
d’améliorer l’accessibilité, la sémantique et augmenter l’interactivité avec
l’utilisateur.
• Cette nouvelle version n’est pas qu’un amas de nouvelles balises. Elle apporte aussi
de nouvelles technologies utilisables au sein du JavaScript.
• Parmi les nouvelles API apportées par le HTML 5, nous en étudierons quatre :
Canvas, Drag & Drop, File et Audio/Video.
34 L’audio et la vidéo
Une des grandes nouveautés du HTML 5 est l’apparition des éléments <audio> et
<video>, qui permettent de jouer des sons et d’exécuter des vidéos, le tout nativement,
c’est-à-dire sans plug-ins tels que Flash, QuickTime ou même Windows Media Player.
Nous allons voir ici comment interagir avec ces deux éléments via le JavaScript.
Pour bien comprendre ce chapitre, il serait bon que vous ayez quelques notions sur ces
deux éléments HTML. Nous vous invitons donc à lire sur OpenClassrooms le chapitre
« La vidéo et l’audio » (https://openclassrooms.com/courses/apprenez-a-creer-votre-
site-web-avec-html5-et-css3/la-video-et-l-audio) du tutoriel de M@teo21 sur le HTML
5. C’est important, car nous ne reviendrons que très sommairement sur l’utilisation
« HTML » de ces deux éléments dans la mesure où nous nous intéressons ici au
JavaScript.
L’audio
Les éléments <audio> et <video> se ressemblent fortement. Ils sont même représentés
par le même objet, à savoir HTMLMediaElement. Ils en possèdent donc les propriétés et
méthodes.
L’insertion d’un élément <audio> est très simple.
<audio id="audioPlayer" src="hype_home.mp3"></audio>
Ce bout de code suffit à insérer un lecteur audio qui lira le son hype_home.mp3. Mais,
nous n’allons pas utiliser l’attribut src, nous lui préférerons deux éléments <source> :
<audio id="audioPlayer">
<source src="hype_home.ogg">
<source src="hype_home.mp3">
</audio>
De cette manière, si le navigateur est capable de lire le format .ogg, il le fera, sinon il
lira le format .mp3. Ceci permet une plus grande interopérabilité (compatibilité entre
les navigateurs et les plates-formes).
Pour afficher un contrôleur de lecteur, il faut utiliser l’attribut booléen controls,
comme ceci : <audio controls="controls"></audio>. Mais nous allons créer notre
propre contrôleur de lecture en JavaScript.
Contrôles simples
Pour commencer, voyons comment créer les boutons Play, Pause et Stop. Nous
accédons tout d’abord à l’élément :
var player = document.querySelector('#audioPlayer');
En revanche, il n’y a pas de méthode stop(). Si nous appuyons sur un bouton Stop, la
lecture s’arrête et repart au début. Pour cela, il suffit de cliquer sur Pause et d’indiquer
que la lecture doit repartir au début, avec la propriété currentTime, exprimée en
secondes :
player.pause();
player.currentTime = 0;
Nous allons créer un petit lecteur, dont voici le code HTML de base :
<audio id="audioPlayer">
<source src="hype_home.ogg">
<source src="hype_home.mp3">
</audio>
Deux boutons ont été placés : le premier est un bouton Play et Pause en même temps
(comme sur la plupart des lecteurs modernes), le second permet de stopper et de
rembobiner la lecture. Voici les fonctions play et resume :
function play(idPlayer, control) {
var player = document.querySelector('#' + idPlayer);
if (player.paused) {
player.play();
control.textContent = 'Pause';
} else {
player.pause();
control.textContent = 'Play';
}
}
function resume(idPlayer) {
var player = document.querySelector('#' + idPlayer);
player.currentTime = 0;
player.pause();
}
Le fonctionnement du bouton Play est simple : avec la méthode paused, nous vérifions
si la lecture est en pause. En fonction du résultat, nous utilisons play() ou pause(), et
nous modifions le libellé du bouton.
(Essayez le code : http://course.oc-static.com/ftp-
tutos/cours/javascript/part5/chap2/ex1.html)
Contrôle du volume
L’intensité sonore se règle avec la propriété volume sur une échelle allant de 0 à 1. Si
le volume est à 0, il est muet ; s’il est à 1, il est au maximum. Pour le diminuer de
moitié, nous indiquerons 0,5. Nous allons réaliser un système très simple : cinq barres
verticales cliquables qui permettront de choisir un niveau sonore prédéfini :
<span class="volume">
<a class="stick1" onclick="volume('audioPlayer', 0)"></a>
<a class="stick2" onclick="volume('audioPlayer', 0.3)"></a>
<a class="stick3" onclick="volume('audioPlayer', 0.5)"></a>
<a class="stick4" onclick="volume('audioPlayer', 0.7)"></a>
<a class="stick5" onclick="volume('audioPlayer', 1)"></a>
</span>
Et la fonction associée :
function volume(idPlayer, vol) {
var player = document.querySelector('#' + idPlayer);
player.volume = vol;
}
Analyse de la lecture
Un élément HTMLMediaElement possède toute une série d’événements pour analyser et
agir sur le lecteur. L’événement ontimeupdate va nous être utile pour détecter quand le
média est en train d’être joué par le lecteur. Cet événement est déclenché
continuellement pendant la lecture.
Ajoutons donc cet événement sur notre élément <audio> :
<audio id="audioPlayer" ontimeupdate="update(this)">
Améliorations
L’interface réalisée précédemment est fonctionnelle, mais rudimentaire. Deux
améliorations principales sont possibles :
• afficher le temps écoulé ;
• rendre la barre de progression cliquable.
function formatTime(time) {
var hours = Math.floor(time / 3600);
var mins = Math.floor((time % 3600) / 60);
var secs = Math.floor(time % 60);
if (hours) {
if (mins < 10) {
mins = "0" + mins;
}
Cet exemple ne permet que de reculer dans la lecture, puisque seule la barre de
progression est cliquable. Il est bien évidemment possible de coder un
système pour avancer dans la lecture, en rendant cliquable le conteneur de la
barre de progression.
do {
top += element.offsetTop;
left += element.offsetLeft;
} while (element = element.offsetParent);
On clique !
Maintenant que nous avons nos fonctions getMousePosition() et getPosition(),
nous pouvons écrire la fonction clickProgress(), qui sera exécutée dès que
l’internaute cliquera sur la barre de progression :
function clickProgress(idPlayer, control, event) {
var parent = getPosition(control); // La position absolue de la
//progressBar
var target = getMousePosition(event); // L'endroit de la progressBar
// où on a cliqué
var player = document.querySelector('#' + idPlayer);
Nous récupérons la distance x, qui est la distance entre le bord gauche de la barre et
l’endroit où nous avons cliqué. Nous divisons x par la largeur totale du conteneur de la
barre de progression (avec offsetWidth) et nous multiplions par 100 pour obtenir un
pourcentage. Ensuite, nous calculons le currentTime en multipliant le temps total de la
chanson par le pourcentage, le tout divisé par 100.
Et n’oublions pas de modifier le code HTML en conséquence :
<div id="progressBar" onclick="clickProgress('audioPlayer', this, event)">Pas de
lecture</div>
Et ça marche !
(Essayez le code : http://course.oc-static.com/ftp-
tutos/cours/javascript/part5/chap2/ex5.html)
La vidéo
Il n’y a pas grand-chose à ajouter sur les lecteurs vidéo. Le principe de fonctionnement
est exactement le même que pour les lecteurs audio. L’élément <video> possède
toutefois quelques propriétés supplémentaires.
PROPRIÉTÉ DESCRIPTION
height Hauteur de la zone de lecture
width Largeur de la zone de lecture
poster Récupère l’attribut poster
videoHeight Hauteur de la vidéo
videoWidth Largeur de la vidéo
En résumé
• Les éléments <audio> et <video> possèdent tous les deux de nombreux attributs et
méthodes afin que chacun puisse créer un lecteur entièrement personnalisé.
• La différence entre les deux éléments est minime. Ils sont tous les deux basés sur le
même objet. L’élément <video> n’apporte que quelques attributs supplémentaires
permettant de gérer l’affichage.
• Contrairement au Flash, la protection du contenu n’existe pas (encore) avec ces deux
éléments. Réfléchissez donc bien avant d’écarter définitivement toute solution avec
Flash !
35 L’élément Canvas
L’élément <canvas> est une zone dans laquelle il va être possible de dessiner grâce au
JavaScript. Cet élément fait son apparition dans la spécification HTML 5, mais il existe
depuis plusieurs années déjà. Il a été développé par Apple pour son navigateur Safari.
Firefox a été un des premiers navigateurs à l’implémenter, suivi par Opera et Chrome.
Les dernières versions d’Internet Explorer supportent également cet élément.
L’API Canvas est un gros sujet qui mériterait un livre à lui tout seul. Tout au long de ce
chapitre d’initiation, nous allons découvrir les bases de ce nouvel élément !
Premières manipulations
Pour commencer, nous insérons le canvas :
<canvas id="canvas" width="150" height="150">
<p>Désolé, votre navigateur ne supporte pas Canvas. Mettez-vous à jour</p>
</canvas>
Une fois que nous avons le canvas, il faut accéder à ce qu’on appelle son contexte, avec
getContext(). Il n’y a pour l’instant qu’un seul contexte disponible : la deux
dimensions (2D). Il est prévu que les navigateurs gèrent un jour la 3D, mais ça reste
expérimental à l’heure actuelle.
Principe de fonctionnement
Dessiner avec Canvas se fait par le biais de coordonnées. Le coin supérieur gauche du
canvas est de coordonnées (0,0). Si nous descendons ou que nous allons vers la droite,
nous augmentons les valeurs. Cela ne change finalement pas trop de ce que nous
connaissons, par exemple pour le positionnement absolu en CSS.
Nous allons utiliser les méthodes pour tracer des lignes et des formes géométriques.
Traçons un rectangle de 50 × 80 pixels :
context.fillStyle = "gold";
context.fillRect(0, 0, 50, 80);
Dans un premier temps, nous choisissons une couleur avec fillStyle, comme un
peintre qui trempe son pinceau avant de commencer son tableau. Puis, avec
fillRect(), nous traçons un rectangle. Les deux premiers paramètres sont les
coordonnées du sommet supérieur gauche du rectangle que nous voulons tracer. Le
troisième paramètre est la largeur du rectangle, et le quatrième est la hauteur. Autrement
dit : fillrect(x, y, largeur, hauteur).
(Essayez le code : http://course.oc-static.com/ftp-
tutos/cours/javascript/part5/chap3/ex1.html)
Si nous voulons centrer ce rectangle, quelques calculs sont nécessaires pour spécifier
les coordonnées :
context.fillRect(50, 35, 50, 80);
Comme il s’agit d’un contour, il est possible de choisir l’épaisseur à utiliser avec la
propriété lineWidth :
context.lineWidth = "5";
context.strokeStyle = "gold";
context.strokeRect(50, 35, 50, 80);
Effacer
Une dernière méthode existe en ce qui concerne les rectangles : clearRect(x, y,
largeur, hauteur). Cette méthode agit comme une gomme, c’est-à-dire qu’elle va
effacer du canvas les pixels délimités par le rectangle. Tout comme fillRect(), nous
lui fournissons les coordonnées des quatre sommets. clearRect() est utile pour faire
des découpes au sein des formes, ou tout simplement pour effacer le contenu du canvas.
context.strokeStyle = "gold";
context.strokeRect(50, 35, 50, 80);
Formes géométriques
Canvas fournit très peu de formes géométriques, à savoir le rectangle et les arcs. Pour
pallier ce manque, Canvas dispose de chemins ainsi que de courbes de Bézier cubiques
et quadratiques.
closePath() n’est pas nécessaire ; il termine le chemin pour nous, en reliant le dernier
point au tout premier. Obtenir une forme fermée, via stroke(), est assez simple. En
revanche, si nous voulons remplir la forme avec fill(), elle sera fermée
automatiquement, closePath() est donc inutile.
(Essayez le code : http://course.oc-static.com/ftp-
tutos/cours/javascript/part5/chap3/ex4.html)
Les arcs
En plus des lignes droites, il est possible de tracer des arcs de cercle, avec la méthode
arc(x, y, rayon, angleDepart, angleFin, sensInverse). Les angles sont
exprimés en radians (oui, rappelez-vous vos cours de trigonométrie !), x et y sont les
coordonnées du centre de l’arc. Les paramètres angleDepart et angleFin définissent
les angles de début et de fin de l’arc.
Pour obtenir des radians, il suffit de multiplier les degrés par π divisé par 180 :
(Math.PI / 180) * degrees.
Utilisation de moveTo()
moveTo() permet de déplacer le « crayon » à l’endroit où nous souhaitons commencer
un chemin. Mais cette méthode peut aussi être utilisée pour effectuer des « levées de
crayon » au sein d’un même chemin :
context.beginPath(); // La bouche, un arc de cercle
context.arc(75, 75, 40, 0, Math.PI);
context.fill();
Une courbe quadratique sera dessinée par quadraticCurveTo(), alors qu’une courbe
cubique le sera par bezierCurveTo() :
quadraticCurveTo(cp1X, cp1Y, x, y)
Les courbes sont définies par leurs points d’arrivée (x et y) et par les points de
contrôle. Dans le cas d’une courbe de Bézier cubique, deux points sont nécessaires. La
difficulté des courbes de Bézier est de connaître les valeurs utiles pour les points de
contrôle. C’est d’autant plus complexe que nous ne voyons pas en temps réel ce que
nous faisons.
Images et textes
Les images
Il est possible d’insérer des images au sein d’un canvas. Pour ce faire, nous utiliserons
la méthode drawImage(image, x, y), mais attention : pour qu’une image puisse être
utilisée, elle doit au préalable être accessible via un objet Image ou un élément <img
/>. Il est également possible d’insérer un canvas dans un canvas. En effet, le canvas que
nous allons insérer est considéré comme une image.
Insérons l’âne Zozor du Site du Zéro (qui figurait sur le logo du site, aujourd’hui
renommé OpenClassrooms), au sein du canvas :
var zozor = new Image();
zozor.src = 'zozor.png'; // Image de 80x80 pixels
Nous aurions pu récupérer une image déjà présente dans la page : <img id=
"myZozor" src="zozor.png" alt="Zozor assis" />
Prenez garde à ne pas insérer des images trop grandes : si le temps de chargement est
trop long, leur affichage sera saccadé au sein du canvas. Une solution est d’utiliser
onload pour déclencher le dessin de l’image une fois qu’elle est chargée :
Mise à l’échelle
drawImage(image, x, y, largeur, hauteur) possède deux paramètres
supplémentaires facultatifs, largeur et hauteur, qui permettent de définir la largeur et
la hauteur que l’image occupera une fois incrustée dans le canvas. Si la diminution de la
taille des images ne pose pas de problèmes, évitez toutefois de les agrandir, au risque
de voir vos images devenir floues.
context.drawImage(zozor, 35, 35, 40, 40);
Ici, l’image est réduite de moitié (sa taille originale est de 80 × 80 pixels).
Recadrage
Quatre paramètres supplémentaires et optionnels s’ajoutent à drawImage(). Ils
permettent de recadrer l’image, c’est-à-dire de prélever une zone rectangulaire afin de
la placer dans le canvas :
drawImage(image, sx, sy, sLargeur, sHauteur, dx, dy, dLargeur, dHauteur)
Toute la difficulté est donc de ne pas s’emmêler les pinceaux dans les paramètres :
var zozor = document.querySelector('#plush');
Les patterns
Imaginons que vous souhaitiez répéter une image pour créer un fond. Vous pourriez
utiliser une double boucle for et insérer plusieurs fois la même image. Mais il y a plus
simple : vous pouvez utiliser les patterns, ou « motifs » en français. Un pattern est une
image qui se répète comme un papier peint. Pour en créer un, il suffit de recourir à la
méthode createPattern(image, type). Le premier argument est l’image à utiliser, le
second est le type de pattern. Différents types existent, mais seul repeat semble
reconnu par la plupart des navigateurs :
var zozor = new Image();
zozor.src = 'zozor.png';
zozor.addEventListener('load', function() {
var pattern = context.createPattern(zozor, 'repeat');
context.fillStyle = pattern;
context.fillRect(0, 0, 150, 150);
});
L’âne Zozor se répète grâce aux patterns.
Vous devez absolument passer par l’événement load car sinon le pattern ne
s’affichera pas correctement si l’image n’est pas chargée.
Le texte
Pour ajouter du texte au sein d’un canvas, vous pourrez utiliser les méthodes
fillText() et strokeText(), secondées par la propriété font, qui permet de définir
le style du texte :
context.fillStyle = "rgba(23, 145, 167, 1)";
context.fillRect(50, 50, 50, 50);
Un quatrième paramètre peut être ajouté : la largeur maximale que le texte doit utiliser.
Lignes et dégradés
Les styles de lignes
Les extrémités
La propriété lineCap permet de définir la façon dont les extrémités des chemins sont
affichées. Trois valeurs sont admises : butt (celle par défaut), round et square. Une
image vaut mieux qu’un long discours, voici donc trois lignes, chacune avec un lineCap
différent :
Les intersections
Pour gérer l’affichage des angles des chemins, vous utiliserez lineJoin. Cette
propriété reçoit elle aussi trois valeurs différentes : round, bevel et miter (celle par
défaut). Comme précédemment, une image sera plus explicite :
Les dégradés
Canvas propose deux types de dégradés : linéaire et radial. Pour créer un dégradé, il
convient tout d’abord de créer un objet canvasGradient que nous assignerons à
fillStyle. Pour créer un tel objet, nous utiliserons createLinearGradient() ou
createRadialGradient(), au choix.
Dégradés linéaires
Quatre paramètres sont nécessaires pour créer un dégradé linéaire :
createLinearGradient(debutX, debutY, finX, finY)
debutX et debutY sont les coordonnées du point de départ du dégradé, finX et finY
sont les coordonnées de fin. Voici un exemple de dégradé :
var linear = new context.createLinearGradient(0, 0, 150, 150);
context.fillStyle = linear;
Cela n’est pas suffisant puisqu’il manque les informations sur les couleurs. Nous allons
les ajouter avec addColorStop(position, couleur). Le paramètre position est une
valeur comprise entre 0 et 1. C’est la position relative de la couleur par rapport au
dégradé. Si nous indiquons 0.5, la couleur commencera au milieu :
var linear = context.createLinearGradient(0, 0, 0, 150);
linear.addColorStop(0, 'white');
linear.addColorStop(1, '#1791a7');
context.fillStyle = linear;
context.fillRect(20, 20, 110, 110);
context.fillStyle = linear;
context.fillRect(10, 10, 130, 130);
Dégradés radiaux
Du côté des dégradés radiaux, six paramètres sont nécessaires :
createRadialGradient(centreX, centreY, centreRayon, finX, finY, finRayon)
Un dégradé radial est défini par deux choses : un premier cercle (le centre), qui fait
office de point de départ, et un second, qui fait office de fin. Les deux cercles n’ont pas
besoin d’avoir la même origine, ce qui permet d’orienter le dégradé :
var radial = context.createRadialGradient(75, 75, 0, 130, 130, 150);
radial.addColorStop(0, '#1791a7');
radial.addColorStop(1, 'white');
context.fillStyle = radial;
context.fillRect(10, 10, 130, 130);
context.fillStyle = radial1;
context.fillRect(10, 10, 130, 130);
context.fillStyle = radial2;
context.fillRect(10, 10, 130, 130);
context.fillStyle = radial3;
context.fillRect(10, 10, 130, 130);
Opérations
L’état graphique
La méthode save() a pour fonction de sauvegarder l’état graphique du canvas, c’est-à-
dire les informations concernant les styles appliqués au canvas. Ces informations sont
fillStyle, strokeStyle, lineWidth, lineCap, lineJoin, translate() et
rotate(), que nous allons découvrir par la suite.
À chaque appel de la méthode save(), l’état graphique courant est sauvegardé dans une
pile. Pour restaurer l’état précédent, il faut utiliser restore().
Les translations
La translation permet de déplacer le repère d’axes du canvas. L’idée est de placer le
point (0,0) à l’endroit où nous souhaitions dessiner une forme. De cette manière, nous la
dessinons sans nous soucier des calculs de son emplacement, ce qui peut se révéler utile
pour l’insertion de formes complexes. Une fois que les formes sont dessinées, nous
replaçons les axes à leur point d’origine. Et bonne nouvelle, save() et restore()
prennent en compte les translations !
Les translations se font avec la méthode translate(x, y). x correspond à l’ampleur
du déplacement sur l’axe des abscisses et y sur l’axe des ordonnées : les valeurs
peuvent donc être négatives.
context.save();
context.translate(40, 40);
context.fillStyle = "teal";
context.fillRect(0, 0, 50, 50);
context.restore();
context.fillStyle = "orange";
context.fillRect(0, 0, 50, 50);
Les rotations
Les rotations permettent de faire pivoter les axes du canvas. Ce dernier tourne alors
autour de son point d’origine (0,0). La méthode rotate() reçoit un seul paramètre :
l’angle de rotation spécifié en radians. Il est possible de combiner une rotation et une
translation, comme le montre l’exemple suivant :
context.translate(75,75);
context.fillStyle = "teal";
context.rotate((Math.PI / 180) * 45);
context.fillRect(0, 0, 50, 50);
context.fillStyle = "orange";
context.rotate(Math.PI / 2);
context.fillRect(0, 0, 50, 50);
context.fillStyle = "teal";
context.rotate(Math.PI / 2);
context.fillRect(0, 0, 50, 50);
context.fillStyle = "orange";
context.rotate(Math.PI / 2);
context.fillRect(0, 0, 50, 50);
Animations
La gestion des animations avec Canvas est quasi inexistante ! En effet, Canvas ne
propose rien pour animer les formes, les déplacer, les modifier… Pour pouvoir
néanmoins créer une animation avec Canvas, il faut :
• dessiner une image ;
• effacer tout ;
• redessiner une image légèrement modifiée ;
• effacer tout ;
• redessiner une image légèrement modifiée ;
• et ainsi de suite.
Ainsi, il suffit d’appeler une fonction qui va redessiner le canvas toutes les x secondes,.
Il est également possible d’exécuter des fonctions à la demande de l’utilisateur, ce qui
est assez simple.
Une question de « framerate »
Framerate est un terme anglais utilisé pour évoquer le nombre d’images affichées par
seconde. Les standards actuels définissent que chaque animation est censée afficher un
framerate de 60 images par seconde pour paraître fluide pour l’œil humain. Parfois, ces
60 images peuvent ne pas être toutes affichées en une seconde à cause d’un manque de
performances, on appelle cela une baisse de framerate et cela est généralement perçu
par l’œil humain comme un ralenti saccadé. Ce problème est peu appréciable et
malheureusement trop fréquent avec les fonctions setTimeout() et setInterval(),
qui n’ont pas été conçues à l’origine pour ce genre d’utilisation…
Une solution à ce problème a été développée : il s’agit de requestAnimationFrame().
À chacune de ses exécutions, cette fonction va déterminer à quel moment elle doit se
redéclencher de manière à garder un framerate de 60 images par seconde. En clair, elle
s’exécute de manière à afficher quelque chose de fluide.
Un exemple concret
Reprenons le canvas que nous venons de réaliser :
En nous basant sur son code, nous allons faire tourner le dessin dans le sens des
aiguilles d’une montre. Pour commencer, il faut créer une fonction qui sera appelée par
window.requestAnimationFrame(). Il s’agira de la fonction draw(angle), qui efface
le canvas et le redessine avec un angle de rotation incrémenté de quelques degrés.
window.addEventListener('load', function() {
var canvas = document.querySelector('#canvas');
var context = canvas.getContext('2d');
function draw(angle) {
context.save();
context.clearRect(0, 0, 150, 150);
context.translate(75,75);
context.fillStyle = "teal";
context.rotate((Math.PI / 180) * (45 + angle));
context.fillRect(0, 0, 50, 50);
context.fillStyle = "orange";
context.rotate(Math.PI / 2);
context.fillRect(0, 0, 50, 50);
context.fillStyle = "teal";
context.rotate(Math.PI / 2);
context.fillRect(0, 0, 50, 50);
context.fillStyle = "orange";
context.rotate(Math.PI / 2);
context.fillRect(0, 0, 50, 50);
context.restore();
angle = angle + 2;
draw(0);
});
Si Canvas vous intéresse, sachez qu’il existe des frameworks qui permettent
de simplifier le dessin, et même d’ajouter des événements aux « formes ».
C’est le cas de KineticJS, dont l’utilisation est expliquée dans ce tutoriel
(https://openclassrooms.com/informatique/cours/piloter-canvas-avec-
kineticjs) de Maurice Chavelli.
36 L’API File
Auparavant, la gestion des fichiers était extrêmement limitée avec le JavaScript. Les
actions possibles étaient peu intéressantes, à la fois pour le développeur et l’utilisateur.
Avec l’API File, le HTML 5 offre désormais davantage de fonctionnalités. Il est
maintenant possible de manipuler un ou plusieurs fichiers afin de les uploader ou
d’obtenir des informations les concernant (par exemple, leur poids).
Dans ce chapitre, nous vous proposons un tour d’horizon de l’API File.
Première utilisation
L’API que nous allons découvrir ne peut pas être utilisée seule. En effet, elle nécessite
d’être appelée par diverses technologies permettant son accès et lui fournissant les
fichiers qu’elle peut manipuler. Cette API a été conçue de cette manière afin d’éviter
que ce ne soit vous, développeurs, qui choisissiez quel fichier lire sur l’ordinateur du
client. Si cette sécurité n’existait pas, les conséquences pourraient être désastreuses.
Sachez qu’à l’heure actuelle, l’API File ne vous permet pas d’écrire un fichier
stocké sur l’ordinateur d’un client ! Vous ne pourrez que le lire ou bien
l’uploader pour le modifier sur un serveur. L’écriture d’un fichier sur
l’ordinateur du client est encore en cours d’étude à l’heure où nous écrivons
ces lignes.
Afin de pouvoir utiliser notre API, nous allons devoir définir comment les fichiers vont
être choisis par l’utilisateur. La solution la plus simple pour commencer est l’utilisation
d’une balise <input type="file" />, qui va nous permettre d’accéder aux propriétés
des fichiers sélectionnés par l’utilisateur. Ces propriétés constituent une partie de l’API
File.
Prenons donc une balise toute simple :
<input id="file" type="file" />
Et ajoutons-lui un événement :
document.querySelector('#file').addEventListener('change', function() {
// Du code…
});
Pour accéder au fichier, nous allons passer par la propriété files de notre balise
<input>. Celle-ci va nous permettre d’accéder à une collection d’objets utilisables de
la même manière qu’un tableau, chaque objet représentant un fichier.
Pourquoi utilisons-nous une collection d’objets, alors que notre input ne nous permet
de sélectionner qu’un seul fichier ? Parce que le HTML 5 a ajouté la possibilité de
choisir plusieurs fichiers pour un seul et même input ! Il vous suffit d’y ajouter
l’attribut multiple pour que cela soit autorisé au client :
<input id="file" type="file" multiple />
La propriété files est la même pour tous, que la sélection de fichiers soit multiple ou
non. Si vous voulez lire le fichier d’un <input> ne gérant qu’un seul fichier, vous
utiliserez files[0]et non pas file.
Maintenant que ce point est éclairci, essayons d’obtenir le nom du fichier sélectionné
grâce à la propriété name contenue dans chaque objet de type File :
document.querySelector('#file').addEventListener('change', function() {
alert(this.files[0].name);
});
L’objet Blob
Un objet de type Blob est une structure représentant des données binaires disponibles
uniquement en lecture seule. La plupart du temps, vous rencontrerez ces objets
uniquement lorsque vous manipulerez des fichiers, car ils représentent les données
binaires du fichier ciblé.
Concrètement, que pouvons-nous faire avec un Blob ? La réponse est pas grand-chose
au final… Enfin, pas en l’utilisant seul tout du moins, car bien que nous ayons la
possibilité de créer un Blob avec l’objet BlobBuilder
(https://developer.mozilla.org/en/Document_Object_Model_%28DOM%29/BlobBuilder
nous ne le ferons quasiment jamais puisque nous utiliserons ceux créés lors de la
manipulation de fichiers.
Les objets Blob possèdent deux propriétés nommées size et type qui permettent
respectivement de récupérer la taille en octets des données manipulées par le Blob ainsi
que leur type MIME (http://fr.wikipedia.org/wiki/Type_MIME).
Il existe également une méthode nommée slice(), mais c’est un sujet bien trop avancé
et peu utile. Si vous souhaitez en savoir plus sur cette fonction, nous vous invitons à
consulter la documentation du MDN (https://developer.mozilla.org/en/DOM/Blob).
L’objet File
Les objets File possèdent un nom bien représentatif puisqu’ils permettent de manipuler
les fichiers. Leur particularité est qu’ils héritent des propriétés et méthodes des objets
Blob, voilà pourquoi nous ne créerons quasiment jamais ces derniers.
Ainsi, en plus des propriétés et méthodes des objets Blob, les objets File possèdent
deux propriétés supplémentaires qui sont name pour obtenir le nom du fichier et
lastModifiedDate pour obtenir la date de la dernière modification du fichier (sous
forme d’objet Date bien évidemment).
Mais ça n’est pas tout. Bien que les objets File ne soient pas intéressants en termes
d’informations, ils le sont davantage lorsque nous commençons à aborder leur lecture,
grâce aux objets de type FileReader.
NOM DESCRIPTION
Stocke les données dans un objet de type
ArrayBuffer. Ces objets ont été conçus pour
permettre l’écriture et la lecture de données
binaires directement dans leur forme native. Ils
readAsArrayBuffer() sont surtout utilisés dans des domaines exigeants
tels que le WebGL
(https://developer.mozilla.org/en/WebGL). C’est
peu probable que vous utilisiez un jour cette
méthode.
Les données sont converties dans un format nommé
DataURL
(http://en.wikipedia.org/wiki/Data_URI_scheme),
qui convertit toutes les données binaires d’un
fichier en base64
(http://fr.wikipedia.org/wiki/Base64) pour ensuite
readAsDataURL()
stocker le résultat dans une chaîne de caractères.
Cette dernière est complétée par la spécification
du type MIME du fichier concerné. Les DataURL
permettent donc de stocker un fichier sous la forme
d’une URL lisible par les navigateurs récents, leur
utilisation est de plus en plus fréquente sur le Web.
Les données ne subissent aucune modification,
readAsText() elles sont tout simplement lues puis stockées sous
forme d’une chaîne de caractères.
Ces trois méthodes prennent chacune en paramètre un argument de type Blob ou File.
La méthode readAsText() possède un argument supplémentaire (et facultatif)
permettant de spécifier l’encodage du fichier, qui s’utilise comme ceci :
reader.readAsText(file, 'UTF-8');
reader.readAsText(file, 'ISO-8859-1');
Avant d’utiliser l’une de ces méthodes, rappelez-vous que nous avons bien précisé que
la lecture d’un fichier est asynchrone. Il faut donc partir du principe que vous allez
avoir plusieurs événements à votre disposition. Ceux-ci diffèrent peu de ceux que nous
rencontrons avec la seconde version de l’objet XMLHttpRequest :
NOM DESCRIPTION
loadstart La lecture vient de commencer.
Tout comme avec les objets XHR, l’événement progress se
déclenche à intervalles réguliers lors de la progression de
la lecture. Il fournit, lui aussi, un objet en paramètre
progress
possédant deux propriétés, loaded et total, indiquant
respectivement le nombre d’octets lus et le nombre
d’octets à lire en tout.
load La lecture vient de se terminer avec succès.
loadend La lecture vient de se terminer (avec ou sans succès).
Se déclenche quand la lecture est interrompue (avec la
abort
méthode abort(), par exemple).
Se déclenche quand une erreur a été rencontrée. La
propriété error contiendra alors un objet de type
error FileError
(https://developer.mozilla.org/en/DOM/FileError)
pouvant vous fournir plus d’informations.
Une fois les données lues, il ne vous reste plus qu’à les récupérer dans la propriété
result. Ainsi, afin de lire un fichier texte, vous pouvez écrire ceci :
<script>
var fileInput = document.querySelector('#file');
fileInput.addEventListener('change', function() {
reader.addEventListener('load', function() {
alert('Contenu du fichier "' + fileInput.files[0].name + '" :\n\n' +
reader.result);
});
reader.readAsText(fileInput.files[0]);
});
</script>
Tout comme avec un objet xhr, vous pouvez vérifier l’état de la lecture, soit avec la
constante :
if(reader.readyState === reader.LOADING) {
// La lecture est en cours...
}
Mise en pratique
L’étude de l’API File est maintenant terminée. Il est probable que vous vous demandiez
encore ce que nous lui trouvons d’exceptionnel… Il est vrai que si nous l’utilisons
uniquement avec des balises <input>, nous sommes assez limités dans son utilisation.
Dans ce chapitre, nous avons abordé les bases de l’API File, à savoir son utilisation
seule, mais il faut savoir que le principal intérêt de cette API réside dans son utilisation
avec d’autres ressources. Nous allons donc étudier comment l’utiliser conjointement
avec l’objet XMLHttpRequest afin d’effectuer des uploads. Et nous verrons au chapitre
37 comment l’utiliser efficacement avec le Drag & Drop.
Pour le moment, nous vous proposons une mise en pratique plutôt sympathique : la
création d’un site d’hébergement d’images interactif. Le principe est simple :
l’utilisateur sélectionne les images qu’il souhaite uploader, elles sont alors affichées en
prévisualisation sur la page et l’utilisateur n’a plus qu’à cliquer sur le bouton d’upload
après avoir vérifié qu’il a sélectionné les bonnes images.
Notre objectif ici est de créer la partie concernant la sélection et la prévisualisation des
images, l’upload ne nous intéresse pas. Afin d’obtenir le résultat escompté, nous allons
devoir utiliser l’API File, qui va nous permettre de lire le contenu des fichiers avant
même d’effectuer un quelconque upload.
Commençons par construire la page HTML qui va accueillir notre script :
<input id="file" type="file" multiple />
<div id="prev"></div>
Rien d’autre n’est nécessaire, nous avons notre balise <input> pour sélectionner les
fichiers (avec l’attribut multiple afin de permettre la sélection de plusieurs fichiers)
ainsi qu’une balise <div> pour y afficher les images à uploader.
Il nous faut maintenant passer au JavaScript. Commençons par mettre en place la
structure principale de notre script :
(function() {
fileInput.addEventListener('change', function() {
});
})();
Ce code déclare les variables et les événements nécessaires. Vous constaterez qu’il
existe une variable allowedTypes, qui contient un tableau listant les extensions
d’images dont nous autorisons l’upload. L’analyse des fichiers peut maintenant
commencer. Sachant que nous avons autorisé la sélection multiple de fichiers, nous
allons devoir utiliser une boucle afin de parcourir les fichiers sélectionnés. Il nous
faudra aussi vérifier quels sont les fichiers à autoriser :
1. fileInput.addEventListener('change', function() {
2.
3. var files = this.files,
4. filesLen = files.length,
5. imgType;
6.
7. for (var i = 0 ; i < filesLen ; i++) {
8.
9. imgType = files[i].name.split('.');
10. imgType = imgType[imgType.length - 1].toLowerCase();
// On utilise toLowerCase() pour éviter les extensions en majuscules
11.
12. if (allowedTypes.indexOf(imgType) != -1) {
13.
14. // Le fichier est bien une image supportée, il ne reste plus
// qu'à l'afficher
15.
16. }
17.
18. }
19.
20. });
Les fichiers sont parcourus, puis analysés. Sur les lignes 9 et 10, nous procédons à
l’extraction de l’extension du fichier en découpant la chaîne de caractères grâce à un
split('.'), et nous récupérons le dernier élément du tableau après l’avoir passé en
minuscules. Une fois l’extension obtenue, nous vérifions sa présence dans le tableau des
extensions autorisées (ligne 12).
Il nous faut maintenant afficher l’image. En HTML, cela se fait grâce à la balise <img>,
or celle-ci n’accepte qu’une URL en guise de valeur pour son attribut src. Nous
pourrions lui fournir l’adresse du fichier à afficher, mais nous ne connaissons que son
nom, pas son chemin. Nous allons donc utiliser les DataURL, qui permettent de stocker
des données dans une URL. Avant de commencer cet affichage, nous plaçons un appel
vers une fonction createThumbnail() à la 14e ligne de notre code :
If (allowedTypes.indexOf(imgType) != -1) {
createThumbnail(files[i]);
}
reader.addEventListener('load', function() {
// Affichage de l'image
});
reader.readAsDataURL(file);
Comme vous pouvez le constater, il n’y a rien de compliqué. Nous instancions un objet
FileReader, nous lui attribuons un événement load, puis nous lançons la lecture du
fichier pour une DataURL. Une fois la lecture terminée, l’événement load se déclenche
si tout s’est terminé correctement, il ne nous reste donc plus qu’à afficher l’image :
reader.addEventListener('load', function() {
});
<div id="prev"></div>
(function() {
function createThumbnail(file) {
});
reader.readAsDataURL(file);
fileInput.addEventListener('change', function() {
imgType = files[i].name.split('.');
imgType = imgType[imgType.length - 1];
if (allowedTypes.indexOf(imgType) != -1) {
createThumbnail(files[i]);
}
});
})();
Ajoutons à cela un code JavaScript qui récupère le fichier spécifié et s’occupe de créer
une requête XMLHttpRequest :
var fileInput = document.querySelector('#file');
fileInput.addEventListener('change', function() {
xhr.addEventListener('load', function() {
alert('Upload terminé !');
});
// Upload du fichier…
});
form.append('file', fileInput.files[0]);
xhr.send(form);
Uploadez un petit fichier (inférieur à 100 Ko) si vous voulez ne pas avoir un
résultat trop long à l’affichage.
L’upload de fichiers par le biais d’un objet xhr ne va pas révolutionner votre façon de
coder. Il permet de simplifier les choses puisque nous n’avons plus à nous embêter à
passer par le biais d’une <iframe>.
Il nous reste une dernière chose à étudier : l’affichage de la progression de l’upload.
L’objet xhr est déjà nettement plus intéressant, n’est-ce pas ?
Nous n’en avons pas encore parlé car vous n’auriez pas pu vous en servir efficacement.
Mais sachez que l’objet xhr possède une propriété upload donnant accès à plusieurs
événements dont l’événement progress. Ce dernier fonctionne exactement de la même
manière que l’événement progress étudié dans le chapitre consacré à l’objet xhr :
xhr.upload.addEventListener('progress', function(e) {
Ainsi, il est facile de créer une barre de progression avec cet événement et la balise
HTML 5 <progress> :
<input id="file" type="file" />
<progress id="progress"></progress>
var fileInput = document.querySelector('#file'),
progress = document.querySelector('#progress');
fileInput.addEventListener('change', function() {
xhr.open('POST', 'upload.html');
xhr.upload.addEventListener('progress', function(e) {
progress.value = e.loaded;
progress.max = e.total;
});
xhr.addEventListener('load', function() {
alert('Upload terminé !');
});
xhr.send(form);
});
Ici, utilisez plutôt un fichier de taille moyenne (2 à 3 Mo) si vous voulez voir
un affichage de la progression.
En résumé
• L’API File permet de manipuler les fichiers au travers d’un objet File qui hérite lui-
même de l’objet Blob, conçu pour manipuler les données binaires.
• Il est maintenant possible de lire le contenu d’un fichier sans avoir à passer par un
quelconque serveur.
• Les fichiers peuvent être utilisés au travers de plusieurs autres technologies telles
que l’Ajax ou la balise <canvas>.
37 Le drag & drop
Le drag and drop (plus communément écrit drag & drop, voire drag’n’drop) est l’un
des principaux éléments d’une interface fonctionnelle. On parle de « glisser-déposer »
en français. Il s’agit d’une manière de gérer une interface en permettant le déplacement
de certains éléments vers d’autres conteneurs. Ainsi, dans l’explorateur de fichiers d’un
système d’exploitation quelconque, vous pouvez très bien faire glisser un fichier d’un
dossier à un autre d’un simple déplacement de souris. Ceci est possible grâce au
concept du drag & drop.
Bien que le drag & drop ait longtemps existé sur les sites web grâce au JavaScript,
aucun système standard n’avait été créé avant le HTML 5, grâce auquel il est maintenant
possible de permettre un déplacement de texte, de fichier ou d’autres éléments depuis
n’importe quelle application vers votre navigateur. Dans ce chapitre, nous verrons
comment utiliser au mieux cette nouvelle API.
Aperçu de l’API
Comme pour les API précédentes, nous allons uniquement survoler l’API Drag & Drop.
Il s’agit d’une simple initiation, qui vous permettra de découvrir les fonctionnalités les
plus importantes. Certains aspects ne seront pas évoqués car ils ne vous seront que très
peu utiles.
En temps normal, vous nous diriez probablement que cette méthode est inutile puisqu’il
suffirait de stocker les données dans une variable plutôt que par le biais de setData().
Ceci est exact si vous travaillez sur la même page. Cependant, le drag & drop en HTML
5 possède la faculté de s’étendre bien au-delà de votre page web et donc de réaliser un
glisser-déposer d’une page à une autre, d’un onglet à un autre ou même d’un navigateur
à un autre. Le transfert de données entre les pages web n’étant pas possible (tout du
moins pas sans « tricher »), il est utile d’utiliser la méthode setData().
document.querySelector('*[draggable="true"]').addEventListener('dragstart',
function(e) {
});
dropper.addEventListener('dragenter', function() {
dropper.style.borderStyle = 'dashed';
});
dropper.addEventListener('dragleave', function() {
dropper.style.borderStyle = 'solid';
});
// Cet événement détecte n'importe quel drag & drop qui se termine, autant
// le mettre sur « document » :
document.addEventListener('dragend', function() {
alert("Un Drag & Drop vient de se terminer mais l'événement dragend ne sait pas
si c'est un succès ou non.");
});
Avant d’essayer ce code, il nous faut réfléchir à une chose : nous appliquons un style
lorsque l’élément déplacé entre dans la zone de drop, puis nous le retirons lorsqu’il en
sort. Cependant, que se passe-t-il si nous relâchons notre élément dans la zone de drop ?
Le style ne disparaît pas puisque l’élément n’a pas déclenché l’événement dragleave.
Il nous faut donc retirer le style en modifiant notre événement drop :
dropper.addEventListener('drop', function(e) {
e.preventDefault(); // Cette méthode est toujours nécessaire pour
// éviter une éventuelle redirection inattendue
alert('Vous avez bien déposé votre élément !');
Voilà tout, essayez donc maintenant de déplacer l’élément approprié à la fois dans la
zone de drop et en-dehors de cette dernière.
(Essayez le code : http://course.oc-static.com/ftp-
tutos/cours/javascript/part5/chap5/ex6.html)
• Récupérer les éventuels fichiers qui ont été déposés par l’utilisateur, le drag & drop
de fichiers étant désormais possible en HTML 5. Cela fonctionne plus ou moins de la
même manière qu’avec une balise <input type="file" />. Il nous faut toujours
accéder à une propriété files, sauf que celle-ci est accessible dans l’objet
dataTransfer dans le cadre d’un drag & drop. Exemple :
dropZone.addEventListener('drop', function(e) {
e.preventDefault();
Mise en pratique
Nous voulons ici créer une page web avec deux zones de drop et quelques éléments que
nous pourrons déplacer d’une zone à l’autre.
Afin de vous faire gagner du temps, voici le code HTML à utiliser et le CSS associé :
<div class="dropper">
<div class="draggable">#1</div>
<div class="draggable">#2</div>
</div>
<div class="dropper">
<div class="draggable">#3</div>
<div class="draggable">#4</div>
</div>
.dropper {
margin: 50px 10px 10px 50px;
width: 400px;
height: 250px;
background-color: #555;
border: 1px solid #111;
border-radius: 10px;
transition: all 200ms linear;
}
.drop_hover {
box-shadow: 0 0 30px rgba(0, 0, 0, 0.8) inset;
}
.draggable {
display: inline-block;
margin: 20px 10px 10px 20px;
padding-top: 20px;
width: 80px;
height: 60px;
color: #3D110F;
background-color: #822520;
border: 4px solid #3D110F;
text-align: center;
font-size: 2em;
cursor: move;
Rien de bien compliqué, le code HTML est extrêmement simple et la seule chose à
comprendre au niveau du CSS est que la classe .drop_hover sera appliquée à une zone
de drop lorsque celle-ci sera survolée par un élément HTML déplaçable.
Pour commencer, nous avons ensuite besoin d’une structure pour notre code :
(function() {
var dndHandler = {
};
})();
Pour exploiter notre structure, nous avons besoin d’une méthode permettant de déplacer
les éléments concernés. Ceux-ci sont ceux qui possèdent une classe .draggable. Afin
de les paramétrer, nous allons créer une méthode applyDragEvents() dans notre objet
dndHandler :
var dndHandler = {
applyDragEvents: function(element) {
element.draggable = true;
};
Ici, notre méthode s’occupe de rendre déplaçables tous les objets qui lui seront passés
en paramètres. Cependant, cela ne suffit pas pour deux raisons.
• Nos zones de drop devront savoir quel est l’élément qui sera déposé, nous allons
utiliser une propriété draggedElement pour sauvegarder cette information.
• Firefox nécessite l’envoi de données avec setData() pour autoriser le déplacement
d’éléments.
Ces deux ajouts sont simples à mettre en place :
var dndHandler = {
applyDragEvents: function(element) {
element.draggable = true;
element.addEventListener('dragstart', function(e) {
dndHandler.draggedElement = e.target;
// On sauvegarde l'élément en cours de déplacement
e.dataTransfer.setData('text/plain', ''); // Nécessaire pour Firefox
});
};
Ainsi, nos zones de drop n’auront qu’à lire la propriété draggedElement pour savoir
quel est l’élément qui a été déposé.
Passons maintenant à la création de la méthode applyDropEvents() qui, comme son
nom l’indique, va se charger de gérer les événements des deux zones de drop. Nous
allons commencer par gérer les deux événements les plus simples : dragover et
dragleave.
var dndHandler = {
// […]
applyDropEvents: function(dropper) {
dropper.addEventListener('dragover', function(e) {
e.preventDefault(); // On autorise le drop d'éléments
this.className = 'dropper drop_hover'; // Et on applique // le style
adéquat à notre zone de drop quand un élément la survole
});
dropper.addEventListener('dragleave', function() {
this.className = 'dropper'; // On revient au style
// de base lorsque l'élément quitte la zone de drop
});
};
Notre but maintenant est de gérer le drop d’éléments. Notre système doit fonctionner de
la manière suivante :
• un élément est « droppé » ;
• notre événementdrop va alors récupérer l’élément concerné grâce à la propriété
draggedElement ;
• l’élément déplacé est cloné ;
• le clone est alors ajouté à la zone de drop concernée ;
• l’élément d’origine est supprimé ;
• le clone se voit réattribuer les événements qu’il aura perdus du fait que la méthode
cloneNode() ne conserve pas les événements.
Ce système est simple à réaliser, voici ce que nous vous proposons comme solution :
dropper.addEventListener('drop', function(e) {
clonedElement = target.appendChild(clonedElement);
// Ajout de l'élément cloné à la zone de drop actuelle
dndHandler.applyDragEvents(clonedElement);
// Nouvelle application des événements perdus lors du cloneNode()
draggedElement.parentNode.removeChild(draggedElement);
// Suppression de l'élément d'origine
});
Nos deux méthodes sont maintenant terminées, il ne nous reste plus qu’à les appliquer
aux éléments concernés :
(function() {
var dndHandler = {
// […]
};
})();
// […]
element.addEventListener('drop', function(e) {
e.stopPropagation(); // On stoppe la propagation de l'événement
// pour empêcher la zone de drop d'agir
});
},
var dndHandler = {
applyDragEvents: function(element) {
element.draggable = true;
element.addEventListener('dragstart', function(e) {
dndHandler.draggedElement = e.target;
// On sauvegarde l'élément en cours de déplacement
e.dataTransfer.setData('text/plain', '');
// Nécessaire pour Firefox
});
},
applyDropEvents: function(dropper) {
dropper.addEventListener('dragover', function(e) {
e.preventDefault(); // On autorise le drop d'éléments
this.className = 'dropper drop_hover'; // Et on applique
// le style adéquat à notre zone de drop quand un élément la survole
});
dropper.addEventListener('dragleave', function() {
this.className = 'dropper'; // On revient au style de base
// lorsque l'élément quitte la zone de
drop
});
dropper.addEventListener('drop', function(e) {
target.className = 'dropper';
// Application du style par défaut
clonedElement = target.appendChild(clonedElement);
// Ajout de l'élément cloné à la zone de drop actuelle
dndHandler.applyDragEvents(clonedElement);
// Nouvelle application des événements perdus lors du cloneNode()
draggedElement.parentNode.removeChild(draggedElement);
// Suppression de l'élément d'origine
});
};
})();
En résumé
• Le drag & drop est une technologie conçue pour permettre un déplacement natif
d’éléments en tous genres (texte, fichiers, etc.).
• Une action de drag & drop nécessite généralement un transfert de données entre
l’élément émetteur et l’élément récepteur, cela se fait généralement par le biais de
l’objet dataTransfer.
• Il est parfaitement possible de déplacer un élément depuis n’importe quel logiciel de
votre système d’exploitation (par exemple, l’explorateur de fichiers) jusqu’à une
zone d’une page web prévue à cet effet.
Sixième partie
Annexe
Aller plus loin
Nous allons aborder ici des notions plus ou moins ignorées dans la structure principale
du cours.
Ainsi, après un petit récapitulatif rapide de certaines notions étudiées, nous vous
présenterons les bibliothèques et les frameworks et ce qu’ils peuvent vous apporter.
Nous parlerons aussi des environnements (autres qu’un navigateur web) qui sont
capables d’exploiter une partie du potentiel du JavaScript.
Récapitulatif express
Ce qu’il vous reste à faire
Avant de nous plonger dans de nouvelles possibilités offertes par le JavaScript, il serait
bien de situer votre niveau. Vous n’êtes pas encore des experts du JavaScript, vous
devrez acquérir davantage d’expérience pour cela ! Si nous avions voulu couvrir toutes
les particularités du JavaScript, ce livre aurait été au moins deux fois plus long, ce qui
n’est pas vraiment envisageable. Vous découvrirez donc ces particularités en
programmant !
Il est important pour vous de programmer, de trouver quelques idées pour des projets un
peu fous. Vous devez coder et vous confronter à quelques problèmes pour pouvoir
progresser ! Vous serez ainsi capables de déboguer vos propres codes, vous
commencerez à éviter certaines erreurs autrefois habituelles, vous réfléchirez de
manière différente et plus optimisée. Ce sont toutes ces petites choses qui feront de vous
un programmeur hors pair utilisant à bon escient tous les outils présentés ici.
Puisque nous n’avons pas pu aborder toutes les notions dans cet ouvrage, vous devrez
consulter de la documentation afin d’approfondir des sujets encore méconnus. Dans tout
le livre, nous avons tâché de vous fournir, quand l’occasion se présentait, des liens vers
la documentation du MDN (https://developer.mozilla.org/fr/) afin que vous preniez
l’habitude de vous en servir. Elles vous seront probablement indispensables tout au long
de votre apprentissage du JavaScript.
Ce que vous ne devez pas faire
L’obfuscation de code
Comme vous avez sûrement pu le constater à travers ce livre, le JavaScript n’est pas un
langage compilé. Il est donc extrêmement simple pour quiconque de lire votre code.
Cependant, il existe une méthode permettant de rendre votre code moins facilement
accessible : l’obfuscation. Cette méthode consiste à noyer une information au sein d’un
flot inutile de données. Dans le cadre de la programmation, cela signifie que vous allez
augmenter la taille de votre code pour y ajouter des instructions inutiles et ainsi perdre
la personne qui voudra tenter de lire votre code.
Mais l’obfuscation de code ne vous apportera vraiment rien mis à part une perte de
performances, d’autant plus qu’un code JavaScript pourra toujours être lu sans trop de
problèmes grâce à divers outils, notamment jsbeautifier.org (http://jsbeautifier.org/).
En revanche, n’hésitez pas à utiliser des outils comme UglifyJS 2 pour minifier vos
codes afin d’éviter de faire télécharger de trop gros fichiers à vos utilisateurs.
Le JavaScript intrusif
Tout au long de ce cours nous avons tenté de vous faire comprendre une notion très
importante : ne faites pas de JavaScript intrusif ! Une page web doit pouvoir fonctionner
sans vos codes JavaScript. Il y a quelques années, ce principe était de rigueur, car
certains navigateurs ne savaient pas encore lire le JavaScript. Les utilisateurs se
retrouvaient donc bloqués sur certaines pages. De nos jours, tous les navigateurs
supportent le JavaScript, mais il est quand même important de garder ce principe de
base à l’esprit afin d’éviter des problèmes de ce genre :
<a href="#" onclick="if(confirm('Êtes-vous sûr ?')) { location =
'http://sitelambda.com'; }">Lien</a>
Dans cet exemple, nous préférons demander confirmation avant que l’utilisateur ne soit
redirigé. Tout naturellement, nous créons donc une condition faisant appel à la fonction
confirm(). Si l’utilisateur est d’accord, nous procédons à la redirection avec l’objet
location (pour plus d’informations sur cet objet, consultez la documentation le
concernant (https://developer.mozilla.org/fr/)).
Mais cette redirection est gênante et ne devrait pas exister. En effet, il est impossible
d’ouvrir le lien cliqué dans un nouvel onglet avec le raccourci Ctrl+clic gauche car
c’est le JavaScript qui définit comment ouvrir la page. Voici un bel exemple de
JavaScript intrusif ! Si vous aviez élaboré votre page web pour qu’elle fonctionne sans
vos codes JavaScript, vous n’auriez pas rencontré ce problème, car vous auriez
probablement codé quelque chose de ce genre :
<a href="http://sitelambda.com" onclick="return confirm('Êtes-vous sûr ?');">Lien</a>
Ici, le JavaScript peut être désactivé sans empêcher le fonctionnement de la page web.
De plus, il ne perturbe pas les fonctionnalités natives du navigateur comme le raccourci
Ctrl+clic gauche.
Bien entendu, il existe certains codes qui ne peuvent pas se passer du JavaScript pour
fonctionner (un jeu, par exemple). Il y aura donc certains cas où vous serez obligés de
faire du JavaScript intrusif.
Étendre le JavaScript
Jusqu’à présent, nous n’avons abordé que le JavaScript dit « pur », c’est-à-dire celui où
vous avez tout développé vous-même, ce qui peut rapidement se révéler fastidieux. Les
animations sont un bon exemple de JavaScript pur : il n’existe aucune fonction native
capable de nous aider dans nos animations, nous devons donc les développer.
Heureusement, le JavaScript est un langage extrêmement utilisé et sa communauté est
immense. Vous trouverez très facilement des scripts adaptés à vos besoins. Ces scripts
sont de deux types : les frameworks et les bibliothèques.
Les frameworks
Le terme « framework » est abusivement utilisé en JavaScript. Ceux que nous
allons vous présenter ne sont pas des frameworks au sens propre du terme,
mais il est courant de les nommer de cette manière.
Les bibliothèques
Contrairement aux frameworks, les bibliothèques (libraries en anglais) ont un but bien
plus précis. Même si un framework vous fournit déjà tout ce dont vous avez besoin, il
sera judicieux d’utiliser une bibliothèque pour deux raisons : le poids du fichier
JavaScript à utiliser (une bibliothèque sera généralement plus légère qu’un framework)
et la spécialisation des bibliothèques. Ces dernières ont souvent tendance à aller plus
loin que les frameworks, vu qu’elles n’agissent que sur un domaine bien précis, ce qui
simplifie d’autant plus votre développement dans le domaine concerné par la
bibliothèque.
Par ailleurs, il existe un principe important dans le développement (web ou logiciel) :
l’optimisation. Utiliser un framework uniquement pour faire une malheureuse animation
n’est vraiment pas conseillé, c’est un peu comme si vous cherchiez à tuer une mouche
avec un tank… Préférez alors les bibliothèques, en voici d’ailleurs quelques-unes qui
pourraient vous servir.
• Sizzle (http://sizzlejs.com/) et Qwery (https://github.com/ded/qwery) : ces deux
bibliothèques sont conçues pour fonctionner de la même manière que la méthode
querySelector(), ce type de bibliothèque est d’ailleurs à l’origine de
l’implémentation officielle de la méthode en question. La première est le moteur de
sélection du framework jQuery mais elle est relativement lourde, la seconde a
l’avantage d’être particulièrement légère et sensiblement plus rapide en termes
d’exécution.
• Popcorn.js (http://popcornjs.org/) : cette bibliothèque permet une manipulation
aisée des balises <audio> et <video>, il devient ainsi très simple d’interagir avec
ces balises afin de mettre en place des sous-titres, des commentaires placés à un
moment précis de la piste audio ou vidéo, etc.
• Raphaël (http://raphaeljs.com/) et Three.js (http://threejs.org/) : ces deux
bibliothèques sont spécialisées dans le graphisme. La première fonctionne
exclusivement avec les images SVG
(http://fr.wikipedia.org/wiki/Scalable_Vector_Graphics) tandis que la seconde s’est
spécialisée dans la 3D et gère à la fois SVG, <canvas> et surtout la bibliothèque
WebGL (http://fr.wikipedia.org/wiki/WebGL), qui sait tirer parti du moteur OpenGL
(http://fr.wikipedia.org/wiki/OpenGL).
• Modernizr (http://modernizr.com/) : comme vous le savez, les langages web sont
plus ou moins bien supportés selon les navigateurs, surtout quand il s’agit de
fonctionnalités récentes. Cette bibliothèque a pour but de vous aider à détecter la
présence de telle ou telle fonctionnalité, il ne vous restera alors plus qu’à fournir un
script résolvant ce problème (un polyfill) ou exploitant une solution alternative.
Il n’est malheureusement pas possible de dresser la liste complète de toutes les
bibliothèques, elles sont bien trop nombreuses.
Voici un site bien utile qui vous permettra de savoir quels navigateurs
supportent telle ou telle fonctionnalité du HTML ou du JavaScript :
http://caniuse.com/. À utiliser sans modération !
Dans un tout autre domaine que le Web, ces dernières années ont vu fleurir différentes
technologies permettant de développer des logiciels en JavaScript, reposant sur HTML
et CSS pour l’interface. On peut citer Adobe AIR qui fut une des premières solutions
aboutie pour exécuter une application JavaScript sous de multiples environnements. On
peut également citer XULRunner, qui sert de base aux logiciels développés par Mozilla
tels que Firefox et le client de messagerie Thunderbird. Depuis, d’autres solutions ont
émergé, les plus notables étant Electron.js (http://electron.atom.io) et NW.js
(http://nwjs.io).
Ces différentes solutions permettent toutes un accès à des API supplémentaires telles
que celles permettant de manipuler des fichiers, d’accéder au disque dur et tout ce qui
permet de créer de vrais logiciels.
Grâce à ces solutions, la création de logiciels en n’utilisant que des technologies
« web » est désormais possible !
Le logo de Node.js
Revenons au domaine du Web, mais côté serveur cette fois-ci ! Il se peut que vous ayez
déjà entendu parler de Node.js (http://nodejs.org/), de quoi s’agit-il ? Node.js est une
plate-forme permettant l’exécution du langage JavaScript côté serveur afin de remplacer
ou venir en complément de langages serveurs plus traditionnels tels que le PHP.
L’intérêt de ce projet réside essentiellement dans son système non bloquant. Prenez le
PHP par exemple, pour lire un fichier en entier, vous serez obligés d’attendre la lecture
complète avant de pouvoir passer à la suite du code. Avec Node.js, vous utilisez un
langage (le JavaScript) conçu pour gérer tout ce qui est événementiel. Vous n’êtes donc
pas d’attendre la fin de la lecture du fichier pour passer à autre chose.
Bien entendu, il ne s’agit pas du seul avantage du projet. Nous vous conseillons de
consulter le site officiel (http://nodejs.org/) pour plus d’informations.
En résumé
• Il vous reste encore beaucoup à faire pour être réellement à l’aise en JavaScript.
Pour cela, vous allez devoir coder afin d’acquérir de l’expérience et gagner en
assurance.
• Évitez au maximum toute obfuscation de code ou tout code intrusif. Cela ne vous
amènera qu’à de mauvaises pratiques et n’aidera pas vos utilisateurs.
• Afin de travailler plus vite, vous trouverez de nombreux frameworks ou
bibliothèques qui vous faciliteront la tâche lors de vos développements. Connaître un
ou deux frameworks sera un avantage pour vous lors d’un entretien d’embauche.
• N’oubliez pas que le JavaScript ne se résume pas à une simple utilisation au sein des
navigateurs web, il existe bien d’autres plates-formes qui s’en servent, tels que
Adobe Air et Node.js.
Ce tutoriel est maintenant terminé ! Nous espérons qu’il aura répondu à vos attentes.
Index
Symboles
<script> 10, 14, 126, 369
A
Ajax 331, 361, 369
Cross-domain 352
Dynamic Script Loading 369
Encodage 347
XMLHttpRequest 337
alert() 11, 14, 55, 56, 88, 169
Animations 287
apply() 235
Array 75, 241, 291
concat() 293
forEach() 313
indexOf() 295
isArray() 299, 311
join() 84
lastIndexOf() 295
length 79, 292
Parcourir avec forEach() 294
pop() 77, 300
push() 77, 300
reverse() 296
shift() 77, 300
slice() 298
sort() 296
splice() 299
toString() 78
unshift() 77, 300
Audio et Video 405
currentTime 406, 409
duration 408
height 412
pause() 406
play() 406
poster 412
stop() 406
videoHeight 412
videoWidth 412
volume 407
width 412
B
Blob 437
Booléens 29
Boucle 45
break 49
continue 49
do while 49
for 50, 79, 93
for in 82, 230
Portée des variables 52
while 47
C
Calculs 23
call() 235
Canvas 415
addColorStop() 427
arc() 419
beginPath() 418
bezierCurveTo() 421
clearRect() 418
closePath() 418
createLinearGradient() 427
createPattern() 424
createRadialGradient() 427
drawImage() 422
fill() 418
fillRect() 416
fillStyle 416, 424
fillStyle() 425
font 425
lineCap 426
lineJoin 426
lineTo() 418
lineWidth 417
moveTo() 418, 420
quadraticCurveTo() 421
restore() 429
rotate() 430
save() 429
stroke() 418
strokeRect() 417
strokeStyle 417
translate() 430
clearInterval() 286
clearTimeout() 286
Closure 319
Commentaire d’encadrement 15
Concaténation 24
Condition
else 35
else if 35
if 33
if else 33
switch 36
Ternaires 39
confirm() 34, 55
console.log() 88, 169
ContentEditable 401
CSS 185
currentStyle 188
getComputedStyle() 188
offsetHeight 189
offsetLeft 189
offsetParent 189
offsetTop 189
offsetWidth 189
style 186
D
Date 282
getDate() 283
getDay() 283
getFullYear() 283
getHours() 283
getMilliseconds() 283
getMinutes() 283
getMonth() 283
getSeconds() 283
getTime() 284
parse() 282
setTime() 284
Débogage 87
Inspecteur web 199
Kit de développement 89
Pile d’exécution 96
Points d’arrêt 92, 202
Décrémentation 45
DHTML 111
document 116
DOM 113, 116
DOM-0 167
DOM-1 114
DOM-2 114, 163, 167
Drag and drop 447
dataTransfert 448
dragend 448
draggable 448
dragstart 448
setData 448
setDragImage() 449
Zone de \« drop \» 449
E
encodeURIComponent() 340
Espace 12
eval() 285
Event 166, 244
clientX 169
clientY 169
Détecter une touche 245
keyCode 244
preventDefault() 172
relatedTarget 169
stopPropagation() 457
target 168
Expressions régulières 251
$ Fin de chaîne 254
[abc] Classe de caractères 255
[^abc] Exclure un caractère 255
[a-z] Intervalle de caractères 255
\b Limite de mot 260
\B Pas une limite de mot 260
\d Caractère décimal 259
\D Caractère non décimal 259
^ Début de chaîne 254
g Option pour répéter une recherche 267
i Option pout ignorer la casse 253
Métacaractères à échapper 258
{n,m} Répété de n à m fois 257
{n} Répété n fois 257
{n,} Répété n fois ou plus 257
\n Retour à la ligne 259
( ) Parenthèses capturantes 263
(? ) Parenthèses non capturantes 265
Quantificateurs 256
* Répété 0, 1 ou plus 256
? Répété 0 ou 1 fois 256
.+ Répété 1 ou plus (non-greedy) 266
+ Répété une ou plus 256
\s Caractère blanc 259
\S Pas un caractère blanc 259
\t Tabulation 259
. Un caractère quelconque 256
\w Caractère de mot 259
\W Pas un caractère de mot 259
F
File 437
FileReader 437
abort 439
error 439
load 439
loadend 439
loadstart 439
progress 439
readAsArrayBuffer() 438
readAsDataURL() 438
readAsText() 438
Fonction
anonyme 66
arguments 61
arguments facultatifs 63
arguments multiples 62
IIFE 69, 115, 194, 323
return 65
zone isolée 68
FormData 357, 444
append() 358
Formulaire
reset() 182
submit() 182
H
HTML 5 399, 405, 415, 435
I
Iframe 361
contentDocument 361
load 363
src 362
Image 303, 422
complete 313
height 304
Lightbox 305
load 304
onload 422
Overlay 306
width 304
Incrémentation 45
Indentation 12
Infinity 278
instanceof 242
Instruction 11
isFinite() 274, 278
isNaN() 100, 101, 115, 274, 278, 279, 280
J
javascript 161
jsFiddle 18
JSON 331, 334, 371
parse() 335, 343
stringify() 335
L
Langage
client-side 5
compilé 4
ECMAScript 7
EX4 7
interprété 4
Java 4, 6, 74, 231
JScript 7
JScript.NET 7
LiveScript 6
précompilé 4
server-side 5
M
Math 273, 274
acos() 276
asin() 276
ceil() 275, 409
cos() 276
floor() 275
max() 276
min() 276
pow() 276
random() 276
round() 275
sin() 276
sqrt() 276
Messaging 403
N
Namespace 231
this 233
Node
appendChild() 138, 140
blur() 183
checked 180
childNodes 134
classList 124
className 123
cloneNode() 143
createElement() 137
createTextNode() 139
disabled 180
firstChild 133
firstElementChild 133, 137
focus() 183
getAttribute() 121
getElementById() 114, 117, 142
getElementsByTagName() 118
hasChildNodes() 144
HTMLElement 121, 231, 313
htmlFor 123, 154
innerHTML 124
innerText 126
insertAfter() 145
insertBefore() 145
lastChild 133
lastElementChild 133, 137
nextElementSibling 137
nextSibling 135
nodeName 132
nodeType 132
nodeValue 134
parentNode 131
previousElementSibling 137
querySelector() 119, 120, 465, 467
querySelectorAll() 119, 120, 195, 294
readonly 180
removeChild() 144
replaceChild() 143
select() 183
selectedIndex 181
setAttribute() 121, 131, 138
textContent 126, 127
value 179
Nombre aléatoire 276
Number 273
NaN 274, 278
O
Object 230, 249
create() 238
valueOf() 248
Objet 73
constructeur 74
littéral 80
méthode 74
natif 75
propriété 74
Opérateur
addition + 23
affectation = 20
division / 23
modulo % 23, 99
multiplication * 23
soustration - 23
Opérateur -- 45
Opérateur ++ 45
Opérateurs de comparaison 30
!= 30
!== 30
< 30
<= 30
=== 30
> 30
>= 30
Opérateurs logiques 31
&& (et) 31
! (négation) 31
|| (ou) 31, 41
Opérateur tilde ~ 246
Orienté objet 73
P
parseFloat() 273, 278
parseInt() 38, 100, 273, 278
Piles et files 300
Point-virgule 11, 67
Polyfill 311
prompt() 25, 55
prototype 228, 229, 231, 313
Q
querySelectorAll() 181
R
RegExp 252, 262
$1, $2, $3... $9 263
exec() 263, 264
test() 252, 263
requestAnimationFrame() 432
S
setInterval() 92, 284
setTimeout() 284, 286, 321
static 326
String 241, 243, 252
charAt() 244
charCodeAt() 244
fromCharCode() 172
indexOf() 245
join() 78
lastIndexOf() 246
length 243
match() 252, 270
replace() 252, 266, 267
search() 252, 270
slice() 247
split() 78, 248, 252, 271
substring() 247
toLowerCase() 132, 243
toUpperCase() 132, 243
trim() 245, 313
valueOf() 248
Style de code 232
switch 36
break 37
case 37
default 37
T
Tableau 75
Index (indice) 76
this 233
Timestamp 281, 284
toString() 236
typeof 22, 248
U
undefined 22, 41, 248
V
Variable
Booléens 22
Chaînes de caractères 21
Numérique 21
Opérateur || (ou) 41
Passage par référence 142
Tester si une valeur existe 40
var 20
Variable globale 59
W
WebSocket 402
Web SQL Database 402
Web Storage 402
window 114, 116
Workers 403
Wrapper 313
X
XML 331, 333
XMLHttpRequest 337, 352, 444
abort 356
abort() 354
error 356
FormData 357
getAllResponseHeaders() 344
load 356
loadend 356
loadstart 356
open() 338
overrideMimeType() 354
progress 357
readyState 341
readystatechange 341
responseText 343
responseXML 343
send() 339
setRequestHeader() 341
status 342
timeout 354
withCredentials 356
Pour suivre toutes les nouveautés numériques du Groupe Eyrolles, retrouvez-nous sur Twitter et
Facebook
@ebookEyrolles
EbooksEyrolles