Manuel

Transcription

Manuel
Génie informatique
Claude Petitpierre, André Maurer
Automne 2009
ii
Table des matières
1 Pages du Web
1.1 Introduction . . . . . . . . . . . . . . . . . .
1.2 Le langage HTML . . . . . . . . . . . . . . .
1.2.1 Qu'est-ce que HTML ? . . . . . . . .
1.2.2 Reconnaître une balise . . . . . . . .
1.2.3 Balises ouvrantes et balises fermantes
1.2.4 Imbriquer des balises . . . . . . . . .
1.2.5 Attributs des balises . . . . . . . . .
1.2.6 Les liens . . . . . . . . . . . . . . . .
1.2.7 Les images . . . . . . . . . . . . . . .
1.2.8 Combiner les balises <img> et <a>
1.2.9 Les tableaux . . . . . . . . . . . . . .
1.2.10 Balises sans contenu . . . . . . . . .
1.2.11 La balise <span> . . . . . . . . . . .
1.2.12 Remarques . . . . . . . . . . . . . . .
1.3 CSS . . . . . . . . . . . . . . . . . . . . . .
1.3.1 Introduction : qu'est-ce que CSS ? . .
1.3.2 La syntaxe . . . . . . . . . . . . . . .
1.3.3 Grouper les sélecteurs . . . . . . . .
1.3.4 Le sélecteur de classe . . . . . . . . .
1.3.5 Lier CSS à un document HTML . . .
1.3.6 Héritage des propriétés . . . . . . . .
1.3.7 Texte et police de caractères . . . . .
1.3.8 Acher et masquer des éléments . .
2 Langage Javascript
2.1
2.2
2.3
2.4
2.5
Page avec code Javascript . .
Variables et aectations . . .
Expressions . . . . . . . . . .
Aectation avec opération . .
Instruction de branchement if
iii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
1
1
1
3
3
5
5
6
7
7
7
8
8
9
9
9
10
11
11
12
13
13
15
17
17
18
20
20
21
iv
TABLE DES MATIÈRES
2.6
2.7
2.8
2.9
2.10
2.11
2.12
2.13
2.14
2.15
2.16
2.17
2.18
2.19
2.20
2.21
2.5.1 Premier type de if . . . . . . . . . . . . . . . . . .
2.5.2 Un piège . . . . . . . . . . . . . . . . . . . . . . .
Expressions Booléennes . . . . . . . . . . . . . . . . . . .
Loi de Morgan . . . . . . . . . . . . . . . . . . . . . . . .
Boucles . . . . . . . . . . . . . . . . . . . . . . . . . . .
Boucle while . . . . . . . . . . . . . . . . . . . . . . . . .
Boucle do while . . . . . . . . . . . . . . . . . . . . . . .
Sorties de boucle . . . . . . . . . . . . . . . . . . . . . .
Imbrication des instructions . . . . . . . . . . . . . . . .
Branchement multiples (switch) . . . . . . . . . . . . . .
Fonctions dénies par le développeur . . . . . . . . . . .
2.14.1 Arguments eectifs et arguments formels . . . . .
2.14.2 Domaines des variables . . . . . . . . . . . . . . .
2.14.3 Nombre variable d'arguments . . . . . . . . . . .
Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . .
Chaînes de caractères . . . . . . . . . . . . . . . . . . . .
2.16.1 Chaînes et délimiteurs . . . . . . . . . . . . . . .
2.16.2 Opérateur concaténation . . . . . . . . . . . . . .
2.16.3 String et nombres . . . . . . . . . . . . . . . . . .
2.16.4 Guillements et apostrophes dans la même chaîne .
2.16.5 Attributs et fonctions de chaînes de caractères . .
Tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.17.1 Création de tableau de nombres . . . . . . . . . .
2.17.2 Tableau vide . . . . . . . . . . . . . . . . . . . . .
2.17.3 Tableaux de strings . . . . . . . . . . . . . . . . .
2.17.4 Tableaux associatifs . . . . . . . . . . . . . . . . .
2.17.5 Aectation de tableaux (doublons) . . . . . . . .
2.17.6 Transformation de tous les éléments . . . . . . . .
2.17.7 Commentaire sur les syntaxes . . . . . . . . . . .
Opérations sur les tableaux et les chaînes . . . . . . . . .
2.18.1 Ordre inverse . . . . . . . . . . . . . . . . . . . .
2.18.2 Tri des éléments d'un tableau . . . . . . . . . . .
2.18.3 Décalages dans des tableaux . . . . . . . . . . . .
2.18.4 Fonction concaténation . . . . . . . . . . . . . . .
Fonctions mathématiques . . . . . . . . . . . . . . . . . .
2.19.1 Fonctions trigonométriques . . . . . . . . . . . . .
2.19.2 Fonctions exponentielles/logarithmiques . . . . .
2.19.3 Arrondis/comparaison/aléatoire . . . . . . . . . .
2.19.4 Constantes mathématiques . . . . . . . . . . . . .
Dates . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Appels diérés ou périodiques . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
21
21
22
23
23
25
25
25
26
27
27
28
29
29
30
31
31
31
32
32
32
35
35
36
36
36
37
39
39
40
40
40
41
42
42
42
43
43
43
43
44
v
TABLE DES MATIÈRES
2.21.1 Appels de base . . . . . . . . . . .
2.21.2 Quelques détails du fonctionnement
2.21.3 Appel d'une instruction . . . . . .
2.21.4 Appel par fermeture . . . . . . . .
2.21.5 Appels périodiques . . . . . . . . .
2.22 Exercices . . . . . . . . . . . . . . . . . . .
2.22.1 ex - Chronomètre . . . . . . . . . .
2.22.2 ex - Devinette . . . . . . . . . . . .
2.22.3 ex* - Triangle de Pascal . . . . . .
2.22.4 ex - Décalages dans un tableau . .
3 Interactions HTML - Javascript
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3.1 Achage - entrée de données . . . . . . . . . . . . . . .
3.1.1 Fenêtres surgissantes . . . . . . . . . . . . . . .
3.1.2 Lecture d'un nombre . . . . . . . . . . . . . . .
3.1.3 Fonction eval . . . . . . . . . . . . . . . . . . .
3.2 Gestion des éléments HTML . . . . . . . . . . . . . . .
3.2.1 Repérage des éléments HTML . . . . . . . . . .
3.2.2 Evénements . . . . . . . . . . . . . . . . . . . .
3.2.3 Symbole this . . . . . . . . . . . . . . . . . . . .
3.2.4 L'objet event . . . . . . . . . . . . . . . . . . .
3.2.5 Traitement multiple des événements . . . . . . .
3.2.6 Introduction de code HTML, innerHTML . . .
3.3 Gestion des cellules d'une table HTML . . . . . . . . .
3.3.1 Tableaux et index des cellules . . . . . . . . . .
3.4 Liste d'éléments actifs - input . . . . . . . . . . . . . .
3.4.1 Champ de texte - text . . . . . . . . . . . . . .
3.4.2 Zone de texte - textarea . . . . . . . . . . . . .
3.4.3 Fenêtre éditable - iframe . . . . . . . . . . . . .
3.4.4 Bouton - button . . . . . . . . . . . . . . . . . .
3.4.5 Case à cocher - checkbox . . . . . . . . . . . . .
3.4.6 Bouton radio - radio . . . . . . . . . . . . . . .
3.4.7 Sélection, simple ou multiple - select . . . . . .
3.5 Gestion des images . . . . . . . . . . . . . . . . . . . .
3.6 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . .
3.6.1 ex - Calcul de moyenne . . . . . . . . . . . . . .
3.6.2 ex - Diverses calculettes . . . . . . . . . . . . .
3.6.3 ex - Mouvement d'une image dans un rectangle
3.6.4 ex - Suivi du curseur . . . . . . . . . . . . . . .
3.6.5 ex - Morpions . . . . . . . . . . . . . . . . . . .
3.6.6 ex - Page structurée . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
44
45
45
46
46
47
47
49
50
51
53
53
53
54
54
54
54
55
56
56
57
58
58
61
62
62
62
63
64
64
64
65
65
66
66
67
68
69
69
70
vi
TABLE DES MATIÈRES
4 Fichiers
4.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . .
4.1.1 Visualisation des chiers dans l'éditeur LemanOS
4.2 Librairie simple . . . . . . . . . . . . . . . . . . . . . . .
4.3 Librairie similaire à celle de Java . . . . . . . . . . . . .
4.3.1 Lecture d'un chier . . . . . . . . . . . . . . . . .
4.3.2 Création / écriture dans un chier . . . . . . . . .
4.3.3 Adjonction à la n d'un chier . . . . . . . . . . .
4.3.4 Elimination d'un chier . . . . . . . . . . . . . .
4.4 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.4.1 ex - Fichier HTML . . . . . . . . . . . . . . . . .
4.4.2 ex - Sauvetage de variables . . . . . . . . . . . . .
5 Arbres DOM et XML
5.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . .
5.2 Représentations graphique . . . . . . . . . . . . . . . . .
5.3 Manipulation d'un arbre au moyen de Javascript . . . . .
5.3.1 N÷uds et leurs attributs . . . . . . . . . . . . . .
5.3.2 Introduction de n÷uds dans l'arbre . . . . . . . .
5.3.3 Elimination de n÷uds . . . . . . . . . . . . . . .
5.3.4 Parcours automatique d'un arbre . . . . . . . . .
5.3.5 Stucture faite au moyen de tableaux associatifs .
5.4 AJAX et chiers XML . . . . . . . . . . . . . . . . . . .
5.4.1 XML . . . . . . . . . . . . . . . . . . . . . . . . .
5.4.2 Lecture d'un chier XML . . . . . . . . . . . . .
5.4.3 Extraction des éléments du DOM . . . . . . . . .
5.4.4 Lecture asynchrone d'un chier XML . . . . . . .
5.4.5 Ecriture d'un chier XML . . . . . . . . . . . . .
5.5 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.5.1 ex - Examen du DOM au moyen du dévermineur
5.5.2 ex - Elément accédé au moyen de Javascript . . .
5.5.3 ex - Parcours d'arbre . . . . . . . . . . . . . . . .
5.5.4 ex - Arbre et tableau associatif . . . . . . . . . .
5.5.5 ex - Lecture d'une structure XML . . . . . . . . .
5.5.6 ex* - Lecture et sauvetage de données en XML . .
6 Librairie graphique, SVG
6.1
6.2
6.3
6.4
Introduction . . . . . . . . . . . .
Contenu d'un chier SVG . . . .
Cadre SVG dans une page HTML
Instructions SVG . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
73
73
73
74
74
74
75
75
75
75
75
76
77
77
78
80
80
81
82
82
83
84
84
85
86
87
88
88
88
89
89
90
91
91
93
93
93
94
95
vii
TABLE DES MATIÈRES
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7.1 Introduction . . . . . . . . . . . . . . . . .
7.2 Les attributs de <canvas> . . . . . . . . .
7.3 Intitialiser <canvas> . . . . . . . . . . . .
7.3.1 document.getElementById(...) . . .
7.3.2 getContext('2d') . . . . . . . . . .
7.3.3 Fonction d'initialisation . . . . . .
7.3.4 Fonction de dessin . . . . . . . . .
7.3.5 Notion de chemin . . . . . . . . . .
7.3.6 Résultat . . . . . . . . . . . . . . .
7.4 Résumé sur l'initialisation de <canvas> . .
7.5 Les images . . . . . . . . . . . . . . . . . .
7.5.1 Création d'une image . . . . . . . .
7.5.2 Dessin d'une image dans <canvas>
7.6 Transformations géométriques . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6.5
6.6
6.7
6.8
6.9
6.10
6.4.1 Ligne . . . . . . . . . . . . . . .
6.4.2 Cercle . . . . . . . . . . . . . .
6.4.3 Ellipse . . . . . . . . . . . . . .
6.4.4 Tracés . . . . . . . . . . . . . .
6.4.5 Polygone (fermé) . . . . . . . .
6.4.6 Ligne polygonale (ouverte) . . .
6.4.7 Rectangle . . . . . . . . . . . .
6.4.8 Rectangle arrondi . . . . . . . .
6.4.9 Image . . . . . . . . . . . . . .
6.4.10 Texte . . . . . . . . . . . . . . .
6.4.11 Texte le long d'un tracé . . . .
Transformations . . . . . . . . . . . . .
Dénitions d'éléments et de groupes . .
Dégradés de couleurs . . . . . . . . . .
6.7.1 Dégradés linéaires . . . . . . . .
6.7.2 Dégradés radiaux . . . . . . . .
Accès aux éléments en Javascript . . .
6.8.1 Créations d'éléments . . . . . .
6.8.2 Modication des éléments . . .
Communications entre HTML et SVG
Exercices . . . . . . . . . . . . . . . . .
6.10.1 ex - Battement de c÷ur . . . .
6.10.2 ex - Editeur simple . . . . . . .
6.10.3 ex - Feux de carrefour . . . . .
6.10.4 ex* - Représentations 3-D . . .
7 Librairie graphique : Canvas
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
95
95
95
95
96
97
97
97
97
98
98
99
100
101
101
102
102
102
103
103
105
105
105
106
107
113
113
113
113
113
114
114
115
116
117
117
117
118
118
118
viii
TABLE DES MATIÈRES
7.6.1
7.6.2
7.6.3
7.6.4
Introduction . . . . . . . . . . . .
Changement d'échelle : scale(x,y)
Translation : translate(dx,dy) . .
Rotation : rotate(angle) . . . . .
8 Objets
8.1 Concept de base . . . . . . . . . . . .
8.1.1 Attributs . . . . . . . . . . .
8.1.2 Méthodes . . . . . . . . . . .
8.2 Copies d'objets . . . . . . . . . . . .
8.3 Création d'objets . . . . . . . . . . .
8.3.1 Utilisation de prototype . . .
8.4 Héritage . . . . . . . . . . . . . . . .
8.4.1 Héritage d'interface . . . . . .
8.4.2 Héritage d'implémentation . .
8.4.3 Tableau dans un objet hérité .
8.5 Fermeture . . . . . . . . . . . . . . .
8.6 Création d'un arbre de tri . . . . . .
8.7 Exercices . . . . . . . . . . . . . . . .
8.7.1 Objets graphiques . . . . . . .
8.7.2 ex* - Héritage . . . . . . . . .
9 Bases de données
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
9.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . .
9.2 Création d'une table (create) . . . . . . . . . . . . . . . .
9.2.1 Types et propriétés des colonnes . . . . . . . . . .
9.3 Achage de la structure d'une table (describe) . . . . . .
9.4 Destruction d'une table (drop) . . . . . . . . . . . . . . .
9.5 Insertion de données (insert) . . . . . . . . . . . . . . . .
9.6 Extraction d'information (select) . . . . . . . . . . . . .
9.6.1 Sélection simple . . . . . . . . . . . . . . . . . . .
9.6.2 Jointures . . . . . . . . . . . . . . . . . . . . . . .
9.6.3 Critères de sélection (where) . . . . . . . . . . . .
9.6.4 Fonction utilisables dans where . . . . . . . . . .
9.6.5 Quelques-unes des fonctions dénies par MySQL .
9.6.6 order by grouping_columns . . . . . . . . . . . .
9.6.7 group by . . . . . . . . . . . . . . . . . . . . . .
9.7 Mise à jour des lignes d'une table . . . . . . . . . . . . .
9.8 Elimination d'une ligne d'une table . . . . . . . . . . . .
9.9 Relations entre tables . . . . . . . . . . . . . . . . . . . .
9.10 Relations n x m . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
118
119
119
119
121
121
121
122
123
123
125
126
126
126
128
129
130
132
132
135
137
137
138
139
140
141
141
142
142
144
146
147
147
150
150
150
151
151
153
ix
TABLE DES MATIÈRES
9.11 Exercices . . . . . . . . . . . . . . . . . . . . . . . . .
9.11.1 ex - Questions sur une structure existante . .
9.12 Accès à la base de données par Javascript . . . . . . .
9.12.1 Librairies . . . . . . . . . . . . . . . . . . . .
9.12.2 Requêtes SQL en Javascript . . . . . . . . . .
9.12.3 Introduction de variables dans la requête SQL
9.12.4 Insertion d'une date . . . . . . . . . . . . . .
9.12.5 Obtention de la clé primaire . . . . . . . . . .
9.12.6 Lecture de toutes les colonnes . . . . . . . . .
9.13 Exercice . . . . . . . . . . . . . . . . . . . . . . . . .
9.13.1 ex - Marchand de vins . . . . . . . . . . . . .
9.13.2 ex - Application de gestion de fournisseurs . .
9.13.3 ex- Organisation d'un musée . . . . . . . . . .
10 Applications pseudo-parallèles
10.1 Tâches pseudo-parallèles . . . . . . . . . .
10.2 Activités dans des objets . . . . . . . . . .
10.2.1 Activité dans plusieurs fonctions . .
10.2.2 Activité gérée par un switch . . . .
10.3 Objets actifs . . . . . . . . . . . . . . . . .
10.3.1 Le compilateur . . . . . . . . . . .
10.4 Communications entre objets . . . . . . .
10.4.1 Appel d'un objet actif . . . . . . .
10.4.2 Boîte aux lettres . . . . . . . . . .
10.4.3 Verrou . . . . . . . . . . . . . . . .
10.4.4 Connexion avec les éléments HTML
10.5 Exercices . . . . . . . . . . . . . . . . . . .
10.5.1 ex - Mouvements de billes . . . . .
10.5.2 ex - St-Nicolas . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
154
154
155
155
156
157
159
159
159
160
160
162
162
165
165
166
166
168
169
170
172
172
174
176
178
179
179
180
x
TABLE DES MATIÈRES
Chapitre 1
Pages du Web
1.1 Introduction
1.2 Le langage HTML
Les pages qui suivent présentent le langage HTML. Il s'agit du langage
qui permet d'acher des informations sur une page web. En particulier, les
points suivants sont discutés.
les principes de base et la structure d'un document HTML
comment mettre en forme du texte (gras, italique, etc...)
comment acher des images
comment mettre du texte dans un tableau
comment créer des liens vers d'autres documents.
1.2.1 Qu'est-ce que HTML ?
Dénition
HTML est un langage pour décrire des pages web.
HTML est un acronyme pour Hyper Text Markup Language (langage
de balisage d'hypertexte)
Exemple de code HTML
Voici un exemple de code HTML :
1
2
CHAPITRE 1.
PAGES DU WEB
<html>
<head>
<title>Le titre de la page</title>
</head>
<body>
<h1>Mon premier titre</h1>
<p>Mon premier <b>paragraphe</b></p>
</body>
</html>
Figure 1.1 Un exemple de code HTML
Lecture du code HTML
Les navigateurs Internet (comme Internet explorer, Firefox ou Chrome
par exemple) sont capables de lire les documents HTML et de les acher,
sous forme de pages web. Certains éléments, comme <html>, <body>, <b>
etc... ne sont pas aché sur la page web. Ces éléments donnent des indications
au navigateur sur la façon d'acher le texte.
Le code HTML ci-dessus, interprété par un navigateur, donne le résultat
suivant :
Figure 1.2 Le code HTML de la gure 1.1 interprété par Google Chrome
1.2.
LE LANGAGE HTML
3
1.2.2 Reconnaître une balise
Document HTML = textes + balises
Pour composer une page web, on écrit donc du texte, et on y insère des
caractères spéciaux qui permettront au navigateur (par exemple Firefox ou
Internet Explorer), de mettre en forme ce texte. Ces caractères spéciaux se
nomment des balises Un document HTML est donc composé de
textes
balises
Sur la gure 1.1, les balises sont
<html>, <head>, <title>, </title>, </head>,
<body>, <h1>, </h1>, <p>, <b>, </b>, </p>,
</body>, </html>
1.2.3 Balises ouvrantes et balises fermantes
Les balises vont par pair : une balise ouvrante et une balise fermante. Les
balises fermantes s'écrivent avec un slash (/). Voici les balises ouvrantes et
les balises fermantes de l'exemple ci-dessus :
Balises ouvrantes : <html>, <head>, <title>, <body>, <h1>, <p>,
<b>
Balises fermantes : </title>, </head>, </h1>, </b>, </p>, </body>,
</html>
Le texte écrit entre la balise ouvrante et la balise fermante est le contenu de
la balise. Dans l'exemple suivant
Mon premier <b>paragraphe</b>
le terme paragraphe est le contenu de la balise <b>.
Sur la gure 1.1 voici le contenu de certaines balises :
contenu de la balise <head> : <title>Le titre de la page</title>
contenu de la balise <h1> : Mon premier titre</h1>
contenu de la balise <body> : <h1>Mon premier titre</h1><p>Mon
premier <b>paragraphe</b></p>
Les balises agissent sur leur contenu. Il existe de très nombreuses balises.
Pour l'instant, nous allons uniquement décrire la signication des balises
utilisées dans l'exemple de la gure 1.1.
4
CHAPITRE 1.
PAGES DU WEB
Structure de base
La structure de base de tout document HTML est la suivante :
<html>
<head>
</head>
<body>
</body>
</html>
L'entier d'un document HTML est contenu entre les balises <html> et
</html>. Entre les balises <head> et </head> se trouvent des informations
supplémentaires, qui n'apparaissent pas directement sur la page, comme par
exemple les styles utilisés sur la page, l'encodage des caractères, le nom donné
à la page, ou encore le code des fonctions qui seront utilisées sur la page.
Les informations qui apparaîtront directement sur la page sont écrites
entre les balises <body> et </body>
<title>
La balise <title>, inscrite dans la partie head correspond en fait au texte
qui sera aché comme titre de l'onglet (voir gure 1.2). Le contenu de cette
balise ne correspond pas à un titre qui apparaîtrait directement sur la page.
<h1>
La balise <h1> signie heading 1. Cette balise permet de faire des titres, à
l'intérieur de la page. Les caractères entre <h1> et </h1> sont donc achés
avec une taille de police plus grande que les autres caractères de la page.
Il est également possible de dénir des sous-titres, des titres de section,
etc... en utilisant les balises suivantes : <h1>, <h2>, <h3>, <h4>, <h5>,
<h6>. <h1> sera utilisée pour les titres principaux, <h2> pour les soustitres, <h3> pour les titres de section, etc...
<p>
La balise <p> permet de dénir les paragraphes
<b>
La balise <b> (abréviation pour bold ) permet de mettre du texte en
gras. Pour mettre du texte en italique, on utilise <i> (pour italic ) ou la
balise <em> (pour emphasis ).
1.2.
5
LE LANGAGE HTML
1.2.4 Imbriquer des balises
Comment mettre à la fois un texte en gras et en italique ? On peut imbriquer les balises. Par exemple, pour écrire
Lausanne est une très belle ville
on écrira le code HTML suivant :
Lausanne est une <b><i>très</i></b> belle ville
Remarque : il serait faux d'écrire le code HTML suivant :
Lausanne est une <b><i>très</b></i> belle ville
En eet, dans cet exemple, l'imbrication des balises n'est pas correcte, car la
balise i, qui est ouverte à l'intérieur de b, se ferme à l'extérieur de b.
Ce problème est similaire à celui des parenthèses en mathématiques. Assimilons un instant la balise b au crochet - <b> correspond à [, </b> correspond à ] - et la balise em à la parenthèse - <em> correspond à ( et </em>
correspond à ) - il est correct d'écrire
2 · [3 − (2x + 3)]
tandis que l'expression n'est pas correcte :
2 · [3 − (2x + 3])
1.2.5 Attributs des balises
Pour expliquer le rôle d'un attribut d'une balise, prenons l'exemple suivant :
Magnifique hôtel
<hr width='150px' align='left'/>
Vue sur le lac
Cet exemple donne le résultat présenté sur la gure 1.3
La balise <hr/> permet d'acher des lignes horizontales. Par défaut, la
ligne sera achée sur toute la largeur de la page. L'attribut width='150px'
permet de dénir la largeur de la ligne à 150 pixels..
Voici quelques caractéristiques d'un attribut :
Pour aecter une valeur à un attribut, on utilise le signe = (égal)
6
CHAPITRE 1.
PAGES DU WEB
Figure
1.3 Une ligne horizontale (<hr>), d'une largeur de 150 pixels,
alignée à gauche
Les attributs s'insèrent après l'élément de la balise et avant le chevron
(>). Les attributs ne se placent que dans la balise d'ouverture, jamais
dans celle de fermeture. Quand il y a plusieurs attributs, ils sont placés
les uns à la suite des autres, dans un ordre quelconque et séparés par
des espaces.
La valeur de l'attribut est entourée par des guillemets (width="150px")
ou des apostrophes (width='150px')
1.2.6 Les liens
La balise <a>, avec son attribut href permet de créer un hyperlien vers
une autre page. Voici un exemple :
<a href="http://www.google.ch">GOOGLE</a>
Quelques explications sur cet exemple :
Dans cet exemple, le terme GOOGLE apparaît sur la page. Lorsque
l'utilisateur clique sur le lien, la page http://www.google.ch apparaîtra.
Un lien, nommé GOOGLE, et pointant vers http://www.google.ch est
créé.
href est l'attribut de la balise a
la valeur de l'attribut href donne l'adresse vers laquelle l'utilisateur sera
dirigé
Le texte qui est placé entre la balise <a...> et <a/> apparaît sur la
page.
1.2.
LE LANGAGE HTML
7
1.2.7 Les images
La balise <img src='...'> permet d'insérer une image dans un document
HTML (voir également le paragraphe 3.5). Cette balise possède un attribut
nommé src qui indique où prendre l'image à acher.
Par exemple, l'instruction suivante
<img src='mon_img.jpg'/>
ache l'image nommée mon_img.jpg sur la page.
1.2.8 Combiner les balises <img> et <a>
Nous avons vu que la balise <a> (et son attribut href) permettent de
mettre un texte en évidence (lien) et de provoquer le passage à une autre page
lorsque l'on clique sur ce lien. En plaçant une balise d'image à la place du
texte à mettre en évidence, nous pouvons faire en sorte que l'image devienne
un lien.
Par exemple les instructions suivantes
<a href='http://www.google.ch'><img src='mon_img.jpg'/></a>
acheront l'image dont la source est mon_img.jpg. Lorsque l'utilisateur
clique sur l'image, la page http://www.google.ch s'achera
1.2.9 Les tableaux
Les tableaux sont créés à l'aide des balises suivantes :
<table> : dénit le tableau
<tr> : dénit une ligne dans le tableau (table row)
<td> : dénit une cellule dans le tableau (table data)
Par exemple, le code suivant produit une table de 2 lignes et 3 colonnes
(voir 1.4)
<table border="1">
<tr>
<td>aaa</td>
<td>b</td>
<td>ccc ccc</td>
</tr>
<tr>
<td>abcde</td>
<td>b</td>
8
CHAPITRE 1.
PAGES DU WEB
<td>ccc</td>
</tr>
</table>
Figure 1.4 Un tableau
1.2.10 Balises sans contenu
Certaines balises ont un contenu (par exemple une <b>belle</b> ville,
le contenu de <b> étant belle ), d'autres n'ont pas de contenu (par exemple
<hr>, ou encore <img src='...'>). Pour ces balises sans contenu, plutôt que
d'écrire la balise ouvrante et la balise fermante (<hr></hr>), on fusionne
ces deux balises, pour n'écrire qu'une balise : <hr/>, ou <img src='...'/>
(où le slash se situe à la n de la balise)
1.2.11 La balise <span>
La balise span n'a aucun eet ! Par exemple, écrire
Un peu de texte
ou
Un <span> peu de </span> texte
produira exactement le même résultat. Cependant, une telle balise est utile
pour délimiter une portion du texte, à savoir le texte présent entre la balise
1.3.
CSS
9
ouvrante <span> et la balise fermante </span>. Dans l'exemple ci-dessus,
il s'agit des termes peu de.
Nous verrons dans la section suivante (1.3), ainsi que dans le chapitre 3
qu'il est parfois utile de pouvoir délimiter une certaine portion du texte, soit
pour la mettre en forme, soit pour la modier.
1.2.12 Remarques
Avec les navigateurs actuels (Internet explorer, Firefox, Chrome, etc...),
même lorsque le code HTML contient certaines erreurs, la page s'achera
malgré tout correctement. Cependant, certains supports n'ont pas les ressources nécessaires pour interpréter du code HTML contenant des erreurs. Il
s'agit par exemple des pages destinées à être vues sur un téléphone portable.
Voici donc certaines règles qu'il convient de respecter, même si les navigateurs actuels acheront correctement le résultat :
l'imbrication des balises doit être correct : Une <b><em>belle</em></b>
ville, et pas Une <b><em>belle</b></em> ville
les balises doivent toujours être fermées : <img src='monImg.jpg'/> et
pas <img src='monImg.jpg'>
les noms des balises et leurs attributs s'écrivent en minuscule : <img
src='...'/> et pas IMG SRC='monImg.jpg'/>
les valeurs des attributs sont toujours encadrées par des guillemets ou
des apostrophes : img src='monImg.jpg' et pas img src=monImg.jpg
1.3 CSS
1.3.1 Introduction : qu'est-ce que CSS ?
Cette section présente le langage CSS. CSS vous permet de dénir l'apparence des textes (comme la police, la couleur, la taille, etc...), ainsi que
l'agencement de la page (comme les marges, l'arrière-plan, etc...). CSS dénit donc la présentation du document.
CSS est l'abréviation de Cascading Style Sheets. Un style dénit la façon
dont un élément HTML (par exemple <h1>) sera aché.
Ces styles peuvent être dénis dans une feuille de style externe (un chier
.css). Une feuille de style peut être utilisée pour dénir la présentation de
plusieurs documents HTML, ce qui permet de gagner beaucoup de temps.
HTML a été conçu pour dénir la structure d'un document pas sa présentation. Par conséquent tout ce qui est lié à la présentation d'un document
10
CHAPITRE 1.
PAGES DU WEB
devrait être déni à l'aide de CSS. Typiquement, il faut préférer CSS à l'utilisation de balises HTML permettant de dénir la présentation d'un document
(comme par exemple <font color='...'>)
1.3.2 La syntaxe
La syntaxe de base de CSS est composée de 3 parties :
un sélecteur
une propriété
une valeur
Ces trois parties sont écrites de la façon suivante :
sélecteur {propriété:valeur}
Un sélecteur correspond à une balise HTML (<p>, <h1>, etc...) et la propriété est un attribut dont on veut changer la valeur.
Exemple :
h1 {font-size:100px}
Dans cet exemple, tous les titres principaux (<h1>) du document auront
une taille de 100 pixels. Si la valeur d'un attribut contient un espace, alors
la valeur de l'attribut s'écrit en guillemets :
h2 {font-family:"sans serif"}
Il est possible de dénir plusieurs attributs pour un même sélecteur. Dans
ce cas, chaque propriété sera séparée par un point-virgule :
p {font-family:"sans serif"; font-size: 90%; color: blue}
En écrivant une propriété par ligne, la lisibilité est meilleure :
p
{
}
font-family:"sans serif";
font-size: 90%;
color: blue;
1.3.
CSS
11
1.3.3 Grouper les sélecteurs
Si certaines propriétés s'appliquent à plusieurs sélecteurs, il est possible
de les grouper. Ainsi, plutôt que d'écrire
h1
{
font-family:"sans serif";
color: blue;
}
h2
{
font-family:"sans serif";
color: blue;
}
Il est possible, et plus rapide, de grouper les sélecteurs h1 et h2 et d'écrire :
h1,h2
{
font-family:"sans serif";
color: blue;
}
1.3.4 Le sélecteur de classe
Avec le sélecteur de classe il est possible d'appliquer des styles diérents
pour des éléments HTML d'un même type. Par exemple, si nous souhaitons
que certains paragraphes (<p>) soient achés en rouge (disons, pour mettre
en évidence des paragraphes importants), et que d'autres paragraphes aient
un alignement centré, nous pouvons dénir deux classes pour chacun de types
de paragraphe :
p.introduction{text-align:center}
p.important{color:red}
Dans le document HTML, l'attribut class permet de choisir la classe qui
sera utilisée :
<p class="introduction">Un paragraphe d'introduction</p>
<p class="important">Un paragraphe important</p>
12
CHAPITRE 1.
PAGES DU WEB
Pour dénir une classe qui s'applique à tous les éléments HTML, et pas
uniquement à un seul élément HTML (comme <p> dans l'exemple ci-dessus),
on utilise la syntaxe suivante : l'élément HTML n'est pas écrit ; on commence
directement par un point, suivi du nom de la classe.
Exemple :
.important{color:red}
Ainsi, la classe important pourra être utilisée pour tous les éléments
HTML :
<h1 class="important">Un titre important</h1>
<p class="important">Un paragraphe important</p>
1.3.5 Lier CSS à un document HTML
Pour intégrer du CSS dans un document HTML, il existe trois possibilités :
une feuille de style externe
une feuille de style interne
un style déni directement dans la balise (inline style )
Les feuilles de style externes
Les feuilles de style externes sont idéales lorsque les même styles sont
appliqués sur plusieurs documents HTML. La balise <link>, à écrire dans la
partie <head> du document HTML permet de lier une feuille de style à un
document HTML :
<head>
<link rel="stylesheet" type="text/css" href="mon_style.css" />
</head>
Généralement, les chiers contenant les styles ont l'extension .css
Les feuilles de style internes
Si le style est appliqué à un seul document HTML, il est possible de dénir
le style, directement dans la partie <head> du document HTML, à l'aide de
la balise <style> :
<head>
<style type="text/css">
h1{font-size:110%}
1.3.
CSS
13
.avertissement{color:red}
</style>
</head>
Inline Styles
Il est possible ne n'appliquer un style qu'à une seule balise. Cette façon
de faire est un peu contraire à l'idée du CSS. En eet, en procédant ainsi, il
n'y a plus de séparation entre la structure d'un document et sa présentation.
Cette façon de faire est donc à utiliser avec précaution.
<img src='bille1.gif'
style='position:absolute; top:50px; left:60px'>
1.3.6 Héritage des propriétés
Pour un même sélecteur, il est possible de dénir plusieurs propriétés,
soit de façon inline, soit dans plusieurs feuilles de style, externes ou internes.
Cet élément héritera de l'ensemble des propriétés dénies dans les diérents
styles (d'où l'appellation Cascading Style Sheet).
Par exemple, si on trouve, dans un chier .css externe la propriété suivante
p{font-size:20px;}
et, à l'intérieur d'un style interne (déni à l'aide d'une balise <style>) la
propriété suivante
p{color:red;}
alors, l'ensemble des paragraphes du document auront les deux propriétés :
les caractères auront une taille de 20 pixels et seront de couleur rouge
1.3.7 Texte et police de caractères
Alignement
La propriété text-align permet l'alignement horizontal des textes ; elle
peut prendre les valeurs suivantes :
center : le texte est centré
left : le texte est aligné à gauche ; il s'agit de la valeur par défaut
right : le texte est aligné à droite
justify : le texte est justié : on modie donc l'espace entre les caractères
pour que le texte soit aligné à la fois à gauche et à droite
14
CHAPITRE 1.
PAGES DU WEB
Par exemple, pour justier les paragraphes, on écrira l'instruction CSS
suivante :
p{text-align:justify}
Police de caractères
La propriété font-family permet de spécier la police de caractères :
h2{font-family:Arial,Verdana,Sans-serif}
Plusieurs polices (séparée par des virgules) peuvent être spéciées. Ainsi, si le
navigateur ne parvient pas à acher la première police (Arial dans l'exemple
ci-dessus), alors il tentera d'acher la seconde police de la liste (Verdana dans
notre exemple). S'il ne parvient pas non plus à acher la police Verdana, alors
il achera une autre police, de la même famille de police (ici : sans-serif). Il
existe trois familles de police :
Serif : police avec de petites extensions (empattement) qui forment la
terminaison des caractères
Sans-serif : police sans empattement
Monospace : police où tous les caractères ont la même largeur
Les exemples ci-dessous montrent la diérence entre une police avec et une
police sans serif. La police Helvetica est une police sans serif tandis que la
AaBbCc
Figure 1.5 Un exemple d'écriture sans serif
police Times est une police serif :
AaBbCc
Figure 1.6 Un exemple d'écriture serif
Tailles de police
La propriété font-size permet de spécier la taille d'un texte :
h1{font-size:40px}
1.3.
CSS
15
Ainsi, les titres auront une taille de 40 pixels. Au lieu de spécier la taille
d'un texte à l'aide de px, correspondant à un nombre de pixels, on peut
également utiliser l'unité em 1 . 1em vaut la taille de police par défaut dans un
navigateur (typiquement 1em=16pixels). Par exemple, si nous décidons que
les titres principaux valent une fois et demi la taille standard des caractères
nous donnerons la valeur 1.5em à la propriété font-size :
h1{font-size:1.5em}
Couleur
La propriété color permet de spécier les couleurs. On peut dénir une
couleur de plusieurs manières :
par son nom (par exemple h1{color :red })
par sa valeur RGB décimale : R indique la quantité de rouge, G indique
la quantité de vert (green) et B indique la quantité de bleu. Les valeurs
sont comprises entre 0 et 255. Par exemple h1{color :rgb(255,0,0)} correspond à la couleur rouge
par sa valeur RGB hexadécimale, où les quantités de rouge, vert et bleu
prennent des valeurs entre 00 et FF 2 : h1{color :#FF0000}
1.3.8 Acher et masquer des éléments
Il est parfois pratique de masquer certaines parties d'une page web. Typiquement, en fonction de choix eectué par l'utilisateur, nous allons acher
certaines informations et en cacher d'autres. Lors de notre étude de Javascript, nous verrons comment, dynamiquement (par exemple lorsque l'utilisateur clique sur un bouton), masquer certaines informations et en acher
d'autres. Pour ce faire, nous allons modier les valeurs des propriétés display
et visibility de certains éléments.
La propriété visibility prend généralement 3 les valeurs visible (l'élément
est visible) ou hidden (l'élément est caché) :
img{visibility:hidden}
1. il est également possible de spécier la taille relative des caractères en pour cent
comme h1{font-size :150%}
2. la base du système hexadécimal est 16. Voici les premières valeurs hexadécimale :
0,1,2,3,4,5,6,7,8,9,10,A,B,C,D,E,F,11,12,13,14,15,16,17,18,19,1A,1B,1C,1D,1E,1F,20,21,
etc...
3. les autres valeurs possible étant collapse (uniquement pour les tableaux, supprime
une ligne ou une colonne), ou inherit (la visibilité est la même que pour l'élément parent)
16
CHAPITRE 1.
PAGES DU WEB
cache les images du document : le reste des éléments de la page ne changent
toutefois pas de place : une zone vide (i.e. correspondant à la couleur d'arrièreplan de la page) apparaît.
Pour masquer un élément, et sans laisser de zones vides sur la page, on
utilise la propriété display, qui prend des valeurs 4 comme none, block ou
inline : none masque l'élément ainsi que la place qu'il occupe (le résultat
est donc le même que si l'élément n'existait pas), block ache l'élément et
ajoute un retour de ligne avant et après l'élément, inline ache simplement
l'élément, sans ajouter de retours de ligne.
4. de nombreuses autres valeurs peuvent également être prises par display, voir la documentation en ligne à ce sujet, comme par exemple www.w3schools.com/css
12 août 2010
Chapitre 2
Langage Javascript
2.1 Page avec code Javascript
La liste présentée ci-dessous correspond à une page HTML contenant du
code Javascript.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=utf-8"/>
<title>aTest</title>
<script type="text/javascript">
var x = 0
function execute (arg) {}
alert(arg)
}
</script>
</head>
<body>
<h1>Partie visible de la page</h1>
<input id='inputID'
onkeypress='if(event.keyCode==13) execute(this.value)'/>
</body>
</html>
Le code Javascript est placé soit dans une balise <script > (lignes 6 à 11),
soit dans les attributs de balises prévues pour cela, tel que celui qui est placé
17
18
CHAPITRE 2.
LANGAGE JAVASCRIPT
à la ligne 15.
On peut également mettre le code Javascript dans un chier séparé et
inclure ce code en utilisant les commandes présentées ci-dessous.
<script src="nomDeFichier.js">
</script>
Il n'y a pas de code entre les deux bornes de la balise.
Le code de Javascript est formé de lignes. Il est possible de mettre deux
instructions sur la même ligne en les séparant par des ; (point-virgule). On
peut rajouter entre les instructions autant de lignes vides et de ; qu'on désire.
En particulier, certains développeurs ajoutent systématiquement un ; à la
n de chaque ligne, car c'est ce que demande Java, un langage bien connu
qui a pratiquement la même syntaxe que Javascript 1 . Par contre un des
successeurs de Java, Scala, n'en demande plus !
Entre les balises <script >, on peut introduire des commentaires, c'està-dire de petits textes qui rappellent au développeur comment est structuré
le programme, mais qui sont ignorés par le moteur du navigateur qui exécute le programme. Ces commentaires peuvent prendre une des deux formes
suivantes (/* */ ou //) :
/* Commentaire sur plusieurs
lignes */
// Commentaire sur une seule ligne
Les premiers commentaires peuvent être placés à l'intérieur d'une ligne,
ou sur plusieurs lignes alors que dans la deuxième forme, le commentaire se
termine à la n de la ligne et n'est donc utilisable que sur une ligne complète
ou à la n d'une ligne.
2.2 Variables et aectations
Les variables, que ce soit en mathématiques ou en informatique, se dénissent principalement par le biais du signe =, mais ce signe a une signication assez diérente dans les deux domaines. Cette section présentent donc
ces diérences.
L'équivalent (approximatif) de l'équation mathématique
23 = 12 + x
s'écrit sous la forme suivante dans la plupart des langages informatiques
(double signe égal)
1. Java est compilé et a des types beaucoup plus rigides que Javascript. Javascript est
interprété, c'est-à-dire que le code est examiné au moment de l'exécution et qu'on peut
donc créer du nouveau code en cours d'exécution
2.2.
VARIABLES ET AFFECTATIONS
19
23 == 12 + x
Quant au signe = utilisé dans la plupart des langages de programmation,
il est réservé à ce qu'on appelle une aectation, c'est-à-dire l'attribution du
résultat d'un calcul à une variable.
En mathématiques, une variable représente un ensemble de valeurs de
façon abstraite ou une valeur particulière qu'il s'agit de déterminer par une
série de transformations des expressions dans laquelle elle est dénie. Par
contre, en informatique, une variable ressemble à un pot dans lequel on dépose
quelque chose. Au cours de l'exécution d'un programme, on dépose même
successivement de nombreuses valeurs diérentes dans la même variable.
On indique à un programme qu'on veut utiliser une variable x en écrivant
simplement var x comme ci-dessous.
var x
x = 23 + 12
Avant qu'une valeur ait été aectée à une variable, le contenu de cette
variable est en général non déni.
Comme on utilise beaucoup de variables, on ne peut pas se contenter d'utiliser les lettres de l'alphabet, même si on y ajoutait les lettres de l'alphabet
grec. On donne donc des noms comprenant plusieurs lettres ou chires. Ces
noms doivent être choisis soigneusement dans les programmes d'une certaine
grandeur (plus grands que les exemples qui suivent) pour pouvoir identier
le rôle des variables et retrouver ce qu'elles contiennent, même quand on a
écrit le programme quelques semaines plus tôt et surtout si une deuxième
personne doit en reprendre un qu'elle n'a pas écrit elle-même.
var x = 1
pot, bidon = 12
Il est possible de donner une valeur initiale dans la déclaration, et de
dénir plusieurs variables dans la même déclaration, comme ci-dessus.
• Les noms des variables commencent toujours par une lettre, un $ ou un
_ (souligné). Dans la suite du nom, on peut en plus utiliser les chires.
• Un nom avec les mêmes lettres qu'un autre, mais dans lequel une lettre
est majuscule alors qu'elle est minuscule dans le premier est considéré
diérent.
• Il est d'usage de commencer les noms par des minuscules et de mettre
une majuscule au milieu si le nom est composé de plusieurs termes,
comme ci-dessous.
nombreDeJours
Si vous ne déclarez pas la variable x avant de l'utiliser, le programme la
déclare lui-même. Nous verrons, à la section 2.14.2, qu'il y a des cas où il est
important de déclarer les variables.
20
CHAPITRE 2.
LANGAGE JAVASCRIPT
2.3 Expressions
On peut écrire toutes les expressions qui utilisent les opérateurs suivants :
+
−
∗
/
%
−−
++
les parenthèses, des variables et des nombres.
Les 4 premiers signes correspondent aux 4 opérations habituelles. L'opérateur % correspond à l'opération modulo, c'est à dire le reste de la division :
107 % 10 produit 7
Un exemple d'expression est donné ci-dessous.
resultat = (nbr*4+(31.0-76))
Attention : on ne peut pas écrire 4y, ni 3(x +1) il faut absolument écrire
4∗y et 3∗(x +1).
Dans l'instruction suivante, l'analogie d'une variable avec un récipient
prend toute sa signication. Cette instruction
x = x + 1
indique que le programme doit prendre la valeur contenue dans x, lui ajouter
1 et déposer cette nouvelle valeur dans x. Si l'on exécute cette instruction
plusieurs fois de suite, x contiendra donc successivement diérentes valeurs.
Les deux derniers signes incrémentent, respectivement décrémentent la
variable à la quelle ils sont accolés, c'est-à-dire ajoutent 1, respectivement
enlèvent 1.
x++
m = y-m = --y
Après la première ligne, x vaut un de plus qu'avant, même s'il n'y a pas
d'aectation. La deuxième ligne enlève 1 à y, puis dépose cette valeur dans
m. La dernière ligne dépose la valeur de y dans m, puis décrémente y. Après
la deuxième ligne, m contient donc la même valeur que y, mais après la
troisième ligne, m contient un de plus que y. Il n'est pas conseillé d'utiliser
les instructions des deux dernières lignes, car elles peuvent prêter à confusion.
Par contre on utilise souvent celle de la première ligne.
2.4 Aectation avec opération
Les diérentes variantes d'aectation sont représentées ci-dessous :
=
+=
−=
∗=
/=
%=
Le premier signe a été déjà présenté. Les autres signes sont à comprendre
de la façon suivante, illustrée par l'aectation + = :
x += 12 est identique à x = x + 12
2.5.
21
INSTRUCTION DE BRANCHEMENT IF
2.5 Instruction de branchement if
2.5.1 Premier type de if
La première forme de l'instruction if est présentée ci-dessous :
if (x<100) {
faire quelque chose }
Lorsque le programme arrive sur la ligne sur laquelle il y a un if, il évalue
ce qu'il y a dans les parenthèses (ici x <100) et si c'est vrai, il exécute faire
quelque chose , par exemple recalculer x ou une autre variable. Si ce n'est
pas vrai, il continue simplement à la ligne suivante.
Notez comment cette instruction est indentée (la marge est diérente pour
chaque ligne). Ce n'est pas indispensable pour le programme, mais très utile
pour celui qui lit le programme.
La deuxième forme de branchement permet de faire soit l'une, soit l'autre
des deux choses. Remarquez que le if ci-dessus est en fait une simplication
du suivant, dans laquelle seul le premier bloc était déni.
if (x==0) {
faire quelque chose } else {
faire autre chose }
Les opérateurs qui permettent de comparer deux valeurs sont décrits cidessous.
==
<
<=
>
>=
!=
Le dernier double signe signie pas égal .
2.5.2 Un piège
Attention au piège suivant. Vous allez y tomber un jour et il est dicile
à voir !
Dans le if, il faut mettre le signe == (double égal). Si l'on écrit if (x =
0), le programme ne réclame pas, car c'est une instruction qui est parfois
utilisée. Dans ce cas, le programme eectue une aectation à x comme si elle
était seule sur une ligne, puis il teste la nouvelle valeur de la variable :
if (x)
Si x est diérente de 0, il passe par la première alternative et sinon, il passe
par la deuxième, et cela indépendamment de la valeur que la variable avait
22
CHAPITRE 2.
LANGAGE JAVASCRIPT
avant le test, seule la nouvelle valeur aectée à la variable est déterminante.
L'éditeur de LemanOS met toutefois l'utilisation d'un simple signe = dans
la condition d'un if en évidence en le marquant en violet !
2.6 Expressions Booléennes
Les expressions dans les parenthèses du if peuvent être plus complexes.
Voici quelques exemples qui vous permettront de comprendre comment les
construire. Les opérateurs ci-dessous permettent de rassembler plusieurs des
comparaisons présentées à la section précédente.
&&
||
!
ces signes signiant ET, OU et PAS
La condition qui exprime si la valeur d'une variable est à l'intérieur d'un
intervalle s'écrit en mathématiques : −12 ≤ y < 33. En programmation, il
faut écrire cela de la façon suivante :
if ((−12 <= y) && (y < 33)) {...}
ou, ce qui revient évidemment au même :
if ((y >= −12) && (y < 33)) {...}
Cette instruction décompose la formule mathématique en deux sous-formules.
Le signe && indique que les deux sous-formules doivent être réalisées : (-12
<= y ) ET (y < 33). Si la première partie est fausse, la deuxième partie ne
peut plus rien changer au résultat du test complet et cette deuxième partie
n'est même pas évaluée, ce qui est parfois utile.
Pour déterminer si la valeur d'une variable est en-dehors d'un intervalle,
il faut écrire y < −12 OU y > 33, le OU s'indiquant par | | . La deuxième
sous-formule n'est testée que si la première est fausse, de façon symétrique
au cas du ET.
Le symbole true est équivalent à 0<1. Le symbole false est équivalent à
0>1. On a donc
true == !false
et, si l'on suppose que bool contient une valeur booléenne (true - false),
if (bool == true)
est équivalent à simplement if(bool)
Pour mettre une valeur dans la variable bool employée ci-dessus, on peut
écrire :
bool = (x<0)
Cette ligne est équivalente à :
if (x<0) {
bool = true
{ else }
bool = false
2.7.
LOI DE MORGAN
23
}
Les accolades peuvent également être omises dans les diérents cas de if,
mais il vaut mieux les mettre, cela évite des erreurs.
2.7 Loi de Morgan
Voici deux exemples pour vous familiariser avec les expressions de comparaison. Les expressions des deux if ci-dessous sont interchangeables. Elles
testent si la valeur de la variable est en dehors de l'intervalle fermé [0 ;10].
if ( (x < 0) | | (x > 10) ) {
. . .
}
ce qui est équivalent à
if ( ! ((y >= 0) && (y <= 10)) ) {
. . .
}
La première instruction teste si la variable est en-dessous de 0 ou audessus de 10, alors que la deuxième teste si PAS la variable entre 0 et 10,
pour l'exprimer littéralement.
On comprend bien que les deux expressions sont identiques. La première
exprime en-dehors de l'intervalle et la deuxième pas dans l'intervalle .
La transformation peut d'ailleurs être faite de façon systématique (loi de
Morgan). Pour cela, il faut remplacer le && par | | ou vice-versa, prendre la
négation de chaque terme et nalement prendre la négation du tout !(...).
Par exemple la négation de (y < 0) s'écrit !(y < 0) et est évidemment
équivalente à (y >= 0).
On pourrait également écrire !( !(x < 0) | | !(x > 10)). Comparez cette
expression avec celle qui est dans les instructions ci-dessus.
Note sur les parenthèses : on peut écrire
if (x>0 && x < y+3) ...
et la machine Javascript comprendra
if ( (x>0) && (x < (y+3)) )
mais il est plus prudent de mettre toutes les parenthèses.
2.8 Boucles
La boucle for permet d'exécuter un certain nombre de fois un groupe
d'instructions et de leur passer à chaque exécution une valeur diérente dans
une variable.
24
CHAPITRE 2.
LANGAGE JAVASCRIPT
Le for contient trois instructions choisies par le programmeur, séparées
par des point-virgules, placées entre parenthèses et suivies d'un bloc d'instructions placées entre deux accolades { }.
var j
for (j=0; j<4 ; j = j+1) {
. . .
}
...suite du programme...
Les trois instructions entre les parenthèses sont utilisées selon le schéma
ci-dessous :
for (initialisation ; tant que ; modification) { }
L'initialisation est eectuée une fois avant de commencer la boucle. Cette
dernière est eectuée tant que la comparaison du milieu est vraie et la variable
est modiée entre chaque tour selon la modication. Evidemment si on ne
met pas de modication, le test aura toujours la même valeur et la boucle
tournera indéniment !
Notez que si cela vous arrive, le navigateur risque de tourner indéniment
dans la boucle et de ne plus vous écouter quand vous lui demandez de fermer
sa fenêtre. Dans ce cas, vous pouvez attendre un moment qu'il se déclenche
de lui-même. Sinon, sur Windows, il faut cliquer avec le bouton droit dans
la barre du bureau qui repère tous les programmes en activité, sélectionner
"Gestionnaire des tâches", sélectionner votre navigateur dans la liste qui
apparaît et cliquer le bouton "Fin de tâche". Vous pouvez également vous
déloguer de votre compte.
Sur MacIntosh, presser alt et cmd simultanément en tapant esc. Sélectionner Firefox et le tuer dans la fenêtre qui apparaît.
La variable de boucle, ici appelée i, peut être déclarée soit par défaut
(section 2.14.2), soit avant, soit dans la boucle comme ci-dessous, ce qui est
la version recommandée.
for (var i=0; i<length; i++) { . . . }
Notez que les accolades peuvent être omises s'il n'y a qu'une seule instruction à répéter, mais il est conseillé de toujours les mettre, sinon on risque
d'oublier de les rajouter quand on complète la boucle, ce qui peut provoquer
des erreurs diciles à trouver.
L'instruction complète peut être mise sur une seule ligne, c'est parfois
utile pour des blocs d'instructions courts.
2.9.
BOUCLE WHILE
25
2.9 Boucle while
La boucle for est parfois trop rigide. On peut alors utiliser l'instruction
suivante, qui est plus simple. Le principe est toutefois similaire et l'on peut
obtenir le même parcours qu'avec le for en plaçant les instructions judicieusement.
Dans l'exemple ci-dessous, la variable j est initialisée à la valeur 0 au
début du programme. Elle est testée dans l'instruction while qui n'exécute
(ou répète) le contenu que tant que l'expression entre parenthèses est vraie.
var j = 0
while ( j<4 ) {
j = j+1
}
...suite du programme...
Il faut donc être sûr que les variables qui sont impliquées dans le test
du while changent au bout d'un certain nombre de tours, sinon le test rendra toujours la même valeur et le programme tournera indéniment dans la
boucle.
A nouveau les accolades pourraient être omises.
2.10 Boucle do while
Dans l'exemple ci-dessous, la variable j est initialisée à la valeur 0 au
début du programme. La boucle est exécutée au moins une fois. Le test
déterminant si la boucle doit continuer est fait à la n de la boucle par
l'instruction while.
do {
. . .
} while (x>0)
Les accolades peuvent être omises.
2.11 Sorties de boucle
Les quatre instructions break, break étiquette, continue et continue étiquette permettent d'interrompre une boucle (toutes les sortes de boucles) ou
de sortir d'un bloc d'instructions encadrée par { } de n'importe quelle position. Une simple boucle innie pourrait donc remplacer les deux while des
pages précédentes qui se terminent avant son bloc, pour l'une, ou après pour
l'autre. Elle pourrait de même remplacer le for.
26
CHAPITRE 2.
LANGAGE JAVASCRIPT
Le test du while ci-dessous est toujours vrai (true ), donc la boucle se
répète indéniment ou jusqu'à ce qu'un test interne l'interrompe. Elle est
précédée d'un nom qui l'identie (étiquette), ce qui permet d'avoir une boucle
dans une autre et d'indiquer de laquelle on veut sortir.
• La première instruction sort de la boucle (break étiquette ) si la condition
du test est vraie.
• La deuxième saute directement à la n de la boucle et la recommence
depuis le début.
maBoucle : while (true) {
j++
if (. . .) {
break maBoucle
}
if (. . .) {
continue maBoucle
}
}
Si l'on imbrique des boucles les unes dans les autres, il faut utiliser les
étiquettes pour indiquer de quelle boucle on sort. Par contre s'il n'y a qu'un
niveau de boucle, on peut laisser tomber l'étiquette.
2.12 Imbrication des instructions
Comme on l'a déjà vu pour les tests dans les boucles for, il est possible
d'imbriquer les instructions décrites dans les paragraphes précédents les unes
dans les autres : des for dans des if, eux-mêmes dans d'autres for ou if. Voici
un exemple d'imbrication de if.
Remarquez l'indentation et reproduisez-la dans vos programmes. Cela facilite grandement le repérage de la structure du programme. Dans LemanOS,
le menu Editer > Indenter fait cela automatiquement.
var y = 24
var x = 12
if ( x > 0 ) {
if ( x == y ) {
. . .
} else {
. . .
}
}
. . .
2.13.
BRANCHEMENT MULTIPLES (SWITCH)
27
Dans cet exemple, le deuxième if n'est exécuté que si le premier a trouvé
x>0
2.13 Branchement multiples (switch)
Le bout de programme montré ci-dessous eectue un ou plusieurs cas à chaque exécution de l'instruction switch. Lorsque le programme arrive à
l'instruction switch, il saute à l'instruction case qui est suivie de la valeur de
l'expression (ici state ) placée entre les parenthèses qui suivent le switch.
Le programme eectue ensuite les instructions qui suivent, puis, s'il y a
un break, il sort du switch, mais sinon continue sur le cas suivant. Il faut donc
faire bien attention de ne pas oublier le break si l'on veut sortir du switch à
la n d'un cas !
var state = 2
switch (state) {
case 2:
. . .
break
case 0:
. . .
case 3:
break
default:
. . .
}
. . .
Comme il n'y a pas de break dans le cas ci-dessus, si l'on entre dans le
case 0, on eectue ensuite le case 3.
Les symboles case doivent être suivis de constantes : strings ou nombres,
mais à la place de state, juste après switch, on pourrait aussi mettre une
expression.
2.14 Fonctions dénies par le développeur
L'appel à la fonction sinus s'écrit en mathématiques sin α, alors qu'en
programmation, on écrirait sin(alpha). Un appel à une fonction produit généralement une valeur, comme dans le cas du sinus, mais ce n'est pas forcément
le cas.
28
CHAPITRE 2.
LANGAGE JAVASCRIPT
Dans l'exemple ci-dessous, la fonction retournera le carré de la valeur
passée en argument.
function carre (x0) {
var y = x0*x0 // pour var voir paragraphe 2.14.2
return y
}
Pour appeler cette fonction, il faut écrire :
m2 = carre(12)
ou, si la valeur à élever au carré est contenue dans une variable, telle que
coteDuCarre :
m2 = carre(coteDuCarre)
2.14.1 Arguments eectifs et arguments formels
Dans l'utilisation d'une fonction, on distingue sa dénition, son appel, ses
arguments et son corps.
Dénition
function nomDeLaFonction (arguments formels) {
. . . // corps de la fonction
return y
}
Appel
ou
x = nomDeLaFonction(arguments effectifs)
nomDeLaFonction(arguments effectifs)
Lorsque le programme exécute l'appel, il transfère les valeurs entre parenthèses (dites arguments eectifs) et les dépose dans des variables (dites
arguments formels) qui ne sont utilisées que dans la fonction. Les arguments
eectifs et les arguments formels peuvent avoir des noms diérents. C'est la
place qu'ils occupent dans la séquence entre les parenthèses qui détermine
leur correspondances.
Après l'appel, les variables formant les arguments eectifs ont les valeurs
aectées dans le corps de la fonctions ou gardent leurs valeurs antérieures suivant qu'elles sont des tableaux ou des valeurs simples (string ou nombres) :
les tableaux sont changés et les variables simples gardent leurs valeurs antérieures.
2.14.
FONCTIONS DÉFINIES PAR LE DÉVELOPPEUR
29
L'instruction return indique quelle valeur retourner à la variable placée
à gauche de l'appel, s'il y en a une. S'il n'y a pas de valeur à retourner, on
peut écrire soit return sans paramètre, soit l'omettre.
L'instruction return peut être placée plusieurs fois dans une fonction, en
particulier pour sortir prématurément de la fonction, de la même façon qu'on
a utilisé l'instruction break.
Le nombre d'arguments peut être quelconque, voire nul. Dans le cas présenté ci-dessous, il y a deux arguments. Cette fonction montre de plus un
cas où il n'y a pas de retour de valeur. Cette fonction n'est utilisée que pour
eectuer l'opération d'achage.
function affiche (a1, a2) {
alert(a1+" "+a2)
}
2.14.2 Domaines des variables
Les variables qui sont déclarées en dehors de toute fonction ou auxquelles
on aecte une valeur sans qu'elles soient déclarées sont connues dans tout le
programme et dans toutes les fonctions. Elles sont appelées globales .
abc = 89
var xyz, lmn
var w = "bonjour", z
Dans les fonctions, les variables déclarées après var et les arguments formels ne sont connus que dans le corps de la fonction. Ces variables sont
appelées locales .
Si l'on appelle une fonction, comme la fonction carre dénie ci-dessus
sans dénir la variable y au moyen de var, la variable serait alors globale. Il
pourrait alors arriver qu'un développeur n'y fasse pas attention, qu'il utilise
lui-même une autre variable y et que l'appel à la fonction lui modie sa
variable, ce qui peut provoquer des erreurs très diciles à détecter.
Il faut donc toujours déclarer les variables utilisées dans les
fonctions au moyen de var, à moins qu'elles ne soient déclarées
dans les arguments formels où qu'elles fassent référence à une variable fondamentale du programme qui doit être connue de tous les
développeurs qui travaillent sur ce projet.
2.14.3 Nombre variable d'arguments
Il est parfois intéressant de créer une fonction qui peut avoir un nombre
d'arguments eectifs diérent à chaque appel. Cela peut être géré de deux
façons.
30
CHAPITRE 2.
LANGAGE JAVASCRIPT
On suppose qu'on veut permettre d'appeler la fonction dénie ci-dessous
avec un ou deux arguments. Pour cela, on teste si le deuxième argument a
été déni dans l'appel avant de l'utiliser, comme indiqué ci-dessous.
function affiche (a1, a2) {
if (a2 == undefined) {
alert(a1)
else {
alert(a1+" "+a2)
}
}
L'autre moyen permet de récupérer un plus grand nombre d'arguments :
function affiche () {
for (var i=0; i<arguments.length; i++) {
var x = arguments[i]
}
}
Le mot réservé arguments est un quasi-tableau qui contient les arguments
eectifs. Ce n'est toutefois pas un vrai tableau, car il ne dénit pas les opérations habituelles des tableaux telles que join ou reverse.
Dans ce quasi-tableau, on ne peut qu'adresser un élément ou connaître
sa longueur, de la façon habituelle indiquée ci-dessus.
2.15 Exceptions
Les exceptions permettent de sortir de la séquence normale du programme
lorsqu'une erreur relativement grave survient. Certaines fonctions de base
transmettent des erreurs sous forme d'exceptions et elles doivent alors être
détectées. En utilisant un dévermineur pour analyser les exceptions, on peut
savoir de façon assez précise où l'erreur est survenue.
Un programme détecte l'erreur qui survient dans une fonction de la façon
suivante.
try{
test(-12)
} catch (e) {
alert(e)
}
En général, l'exception obtenue dans e contient un champ e.message et un
champ qui montre la pile d'exécution, c'est-à-dire l'imbrication des fonctions
qui s'étaient appelées en cascade au moment de l'erreur.
2.16.
CHAÎNES DE CARACTÈRES
31
Si le développeur désire installer un tel mécanisme dans une de ses fonctions, il doit introduire l'instruction throw à l'endroit où l'erreur est détectée.
Dans le cas ci-dessous, l'exception ne contiendra qu'une chaîne de caractères.
Pour plus d'information, voir Internet ou un livre plus complet.
function test (m) {
if (m<0) {throw 'erreur détectée'}
alert(m)
}
}
2.16 Chaînes de caractères
2.16.1 Chaînes et délimiteurs
Les variables peuvent contenir des mots ou des phrases, y compris des
nombres sous formes de textes. Ces éléments sont appelés String (corde ou
celle en français). Des exemples sont donnés ci-dessous. Les strings sont
encadrées soit par des apostrophes ', soit par des guillements ".
var x = 'une string'
var y = "une autre string"
Dans une chaîne encadrée par des " (guillemets), on peut introduire des
' (apostrophes) et vice-versa.
x = "une string n'est pas un nombre"
y = 'les "strings" sont des chaînes de caractères'
2.16.2 Opérateur concaténation
On peut construire une nouvelle string en mettant d'autres strings bout
à bout. Cette opération s'appelle concaténation. Pour indiquer une concaténation, on sépare les strings par des +, comme ci-dessous :
var y = "une " + 'string'
Le programme ne stocke pas les délimiteurs, on peut donc mélanger les
deux types de strings dans la même concaténation.
Si Javascript trouve un + entre deux strings, il sait qu'il faut les concaténer et s'il en trouve un entre deux nombres, qu'il faut les additionner.
Lorsque l'on concatène des strings (encadrées de guillemets) et des variables (sans guillemets), les règles sont les mêmes que celles que l'on a vues
dans le dernier paragraphe. On peut donc mélanger les chaînes et les variables
dans une même concaténation. L'instruction
var x = "txt" + txt
32
CHAPITRE 2.
LANGAGE JAVASCRIPT
produira donc la chaîne txtblabla, si la variable txt contient blabla
2.16.3 String et nombres
Si l'on concatène une string avec un nombre, le programme convertit
automatiquement le nombre en string avant la concaténation. Si l'on veut
utiliser les deux sortes de + (addition et concaténation) dans la même chaîne,
il faut mettre des parenthèses. S'il n'y a que des nombres à l'intérieur de
parenthèses, le + est considéré comme addition.
Ainsi on a les résultats suivants
12 + 12 == 24
"12" + "12" == "1212"
12 + "12" == "1212"
(6+6) + "12" == "1212"
6 + (6+"12") == "6612"
2.16.4 Guillements et apostrophes dans la même chaîne
Nous avons vu qu'il était possible de concaténer deux strings, dont l'une
est encadrée par des guillemets et l'autre par des apostrophes. Si nous voulons
faire apparaître les deux signes à l'intérieur de la même string, ce n'est pas
directement possible, mais on peut utiliser un truc. Il faut concaténer deux
strings dont l'une contient le premier signe et l'autre le deuxième.
'Une "string" ' + "n'est pas un nombre"
En Javascript, mais pas en HTML, il est également de faire précéder les
" et les ' qui ne sont pas des délimiteurs par des
'Une "string" n\'est pas un nombre'
"Une \"string\" n'est pas un nombre"
2.16.5 Attributs et fonctions de chaînes de caractères
Les paragraphes qui suivent présentent les fonctions les plus fréquemment
utilisées ainsi que leur eet sur la chaîne dénie ci-dessous :
var chaine = "aaa8bbb8aaa"
length
chaine.length // fournit le nombre de caractères qui constituent la
chaîne. Rappelez-vous que le dernier caractère porte le rang chaine.length−1,
parce que la chaîne commence en 0.
2.16.
CHAÎNES DE CARACTÈRES
33
substring
Cette fonction retourne la sous-chaîne délimitée par ses deux arguments.
Le caractère indicé par le premier argument fait partie de la sous-chaîne
retournée. Le caractère indicé par le deuxième argument n'en fait pas partie.
chaine.substring(4,7) // retourne "bbb"
chaine.substring(0, chaine.length) retourne donc la chaîne initiale
charAt, charCodeAt
Ces deux fonctions retournent le charactère placé au rang indiqué en
argument. Le deuxième est équivalent à la syntaxe des tableaux.
chaine.charAt(2) // retourne "a"
chain[2] // retourne "a"
La fonction ci-dessous retourne le code attribué au code du caractère placé
au rang indiqué en argument.
chaine.charCodeAt(2) // retourne 97, le code ASCII de "a"
Notez qu'il n'est pas possible de modier un caractère d'une chaîne, pour
des raisons de abilité des programmes (voir section 2.17.5).
indexOf, lastIndexOf
Cette fonction retourne le rang de la première (respectivement dernière)
apparition de la sous-chaîne placée en argument. Si la sous-chaîne n'est pas
présente dans la chaîne, la fonction retourne -1.
chaine.indexOf("aaa") // retourne 0
chaine.lastIndexOf("aaa") // retourne 8
replace
Cette fonction retourne une nouvelle chaîne dans laquelle la première
apparition des caractères entre les / ont été remplacés par ceux qui sont
placés dans le deuxième argument.
chaine.replace(/aaa/, zzz) // retourne "zzz8bbb8aaa"
Si l'on ajoute la lettre g aprés le deuxième /, toutes les apparitions des
aaa vont être changées.
chaine.replace(/8/g, "<br>") // retourne "aaa<br>bbb<br>aaa"
Si l'on ajoute la lettre i, la recherche se fait sans tenir compte des majuscules (a == A). Il est possible de mettre les deux lettres : i et g.
34
CHAPITRE 2.
LANGAGE JAVASCRIPT
search
Cette fonction est semblable à indexOf, mais elle utilise une expression
régulière, /expr/ au lieu d'une chaîne de caractères, ce qui permet de faire
une recherche en considérant l'égalité des majuscules et des minuscules.
La syntaxe choisie pour le premier argument des deux fonctions précédentes est particulièrement malheureuse, car il est très impossible de la distinguer des commentaires /* */ et des quotients successifs 36 / 4 / 3. L'éditeur
de LemanOS ache d'ailleurs des erreurs dans quelques cas, car il aurait été
trop dicile de traiter tous les (rares) cas. Il existe toutefois une syntaxe plus
standard :
/cc/g
est équivalent à
new RegExp("cc", "g")
Ainsi, l'équivalent de la ligne ci-dessus est
chaine.replace(new RegExp("8","g"), "<br>")
// retourne "aaa<br>bbb<br>aaa"
split
Cette fonction permet de découper une chaîne en la sectionnant à tous
les endroits où la sous-chaîne passée en argument est présente dans la chaîne
initiale. Cette fonction retourne un tableau de chaînes, dont chaque élément
correspond à une partie découpée. Si la chaîne commence ou nit par la souschaîne dénissant le découpage, une chaîne nulle est ajoutée en première,
respectivement en dernière, position du tableau.
chaine.split("8") // retourne ["aaa", "bbb", "aaa"]
chaine.split("aaa") // retourne ["", "8bbb8", ""]
join
Cette fonction a l'eet inverse de split, elle recolle les éléments d'une
tableau en une seule chaîne, en ajoutant entre les morceaux la chaîne placée
en argument.
var x = chaine.split("8")
x.join("9") // retourne "aaa9bbb9aaa"
La fonction split suivie de la fonction join peut être utilisée pour eectuer
un remplacement. L'exemple ci-dessus revient en eet à remplacer les 8 par
des 9. Attention, si l'on ne met pas d'argument dans join, la fonction ajoute
des ",". Pour ne rien ajouter entre les morceaux, il faut donc utiliser :
x.join("") // concatène les éléments du tableau en un seul morceau.
2.17.
TABLEAUX
35
2.17 Tableaux
2.17.1 Création de tableau de nombres
Les tableaux groupent des variables semblables sous un même nom. Il est
possible d'accéder à chaque variable (élément) d'un tableau, casier, à l'aide
d'un index.
Dans l'exemple ci-dessous, la variable casier est dénie comme un tableau
rempli avec les quatre valeurs 3, 5, 9, 2.
var unit
var casier = [3, 5, 9, 2]
unit = casier[ 2 ] // c'est-à-dire 9
On peut accéder aux éléments de ce tableau au moyen de casier[0], casier[1], casier[2], casier[3]. Le nombre placé entre les parenthèses s'appelle
l'index. L'index peut être fait de n'importe quelle expression (entière ; π , par
exemple, est arrondi à la valeur 3).
Les index des éléments de casier vont donc de 0 à 3. Remarquez que 3,
le dernier index, est égal à la longueur du tableau 1. Cette numérotation
partant de 0 peut paraître étrange a priori, mais elle a des avantages que
nous verrons en cours de route.
On peut également créer un tableau de la façon suivante. Les deux lignes
sont équivalentes (notez la majuscule au début d'Array ).
var liste = new Array( 5, 4, 4, 2 )
var casier = [ 5, 4, 4, 2 ]
Attention, si l'on ne met qu'un argument, tel que new Array(2), le tableau
sera créé avec deux positions, il ne sera pas initialisé avec la valeur 2 dans
la première position. Par contre [2] crée bien un tableau à un seul élément,
dont la valeur vaut 2.
L'exemple ci-dessous montre qu'un index peut être calculé de nombreuses
façons diérentes. x et y recevront les deux 5, car 1+3 ausssi bien que tbl[2]
produisent 4, qui est mis à son tour dans l'index de tbl.
var tbl = [1, 2, 4, 3, 5]
var x = tbl[ 1+3 ]
var y = tbl[ tbl[2] ]
Pour déposer une valeur dans un tableau, on écrit simplement :
tbl[7] = 99
Si l'on introduit un élément à une position qui n'a pas encore été dénie,
le tableau est automatiquement allongé.
On obtient la longueur d'une tableau en ajoutant .length après le nom du
tableau. Ainsi pour acher successivement tous les éléments d'un tableau,
on peut écrire la boucle suivante :
36
CHAPITRE 2.
LANGAGE JAVASCRIPT
for (var k=0; k<tbl.length; k++) {
var x = tbl[k]
}
2.17.2 Tableau vide
Souvent on ne sait pas combien d'éléments il faut prévoir dans le tableau
ni ce qu'ils seront. Dans ce cas, on peut créer le tableau de la façon suivante :
var casier = new Array()
casier[ 2 ] = 2
Au départ le tableau est vide, mais le moteur du navigateur aggrandira le
tableau si on lui demande de déposer une valeur à une position qui n'existe
pas encore.
On peut également créer un tableau vide au moyen de :
liste = [ ]
On peut même changer la dimension d'un tableau en écrivant tbl.length
= 33.
2.17.3 Tableaux de strings
Les tableaux peuvent également contenir des chaînes de caractères (string ).
L'adressage des éléments se fait de la même manière que pour les nombres.
var liste = ['bleu', 'rouge'] // valeurs initiales
var nouveau = liste[ 1 ]
(1)
2.17.4 Tableaux associatifs
Les index des arrays peuvent eux-mêmes être constitués de chaînes de
caractères (string ), comme dans l'exemple suivant :
var couleurDrapeau = [ ]
couleurDrapeau['croix'] = 'blanc'
couleurDrapeau['bord'] = 'rouge'
var nouveau = couleurDrapeau[ 'croix' ]
Ainsi la variable nouveau contiendra la chaîne 'blanc'.
Notez que l'attribut length n'est plus déni pour les tableaux associatifs.
En utilisant ces tableaux, on pourrait créer un dictionnaire passant d'une
langue à l'autre, en mettant les mots d'une langue dans les cellules adressées
par les mots de l'autre langue. Cependant, la traduction n'irait que dans un
sens et elle serait un peu trop simpliste.
Une façon d'initialiser les tableaus associatifs est indiquée ci-dessous.
Cette façon sera revue lorsque nous verrons les objets (section objets)
2.17.
37
TABLEAUX
couleurDrapeau = {croix: 'blanc', bord:"rouge"}
(2)
Notez qu'on pourrait également mettre croix et bord entre guillemets, ce
qui serait indispensable si le nom de l'index était par exemple 'bord-drapeau'
pour éviter d'avoir le signe moins dans un symbole.
Pour parcourir toutes les entrées d'un tableau associatif, on ne peut pas
écrire une boucle for normale, puisqu'un tel tableau n'a pas d'index. La
boucle qui fait ce parcours est présentée ci-dessous :
for (var clef in couleurDrapeau) {
alert(clef+" > "+couleurDrapeau[clef])
}
(3)
Les textes achés dans la fenêtre d'alert utilisées ci-dessus sont les suivantes :
bord > rouge
croix > blanc
Un tableau associatif permet de créer une liste de mots, en éliminant
automatiquement les doublons. Pour cela on l'utilise de la manière suivante :
var liste = {}
liste["aaa"] = true
liste["bbb"] = true
liste["aaa"] = true
Seules les lignes 2 et 3 introduisent des valeurs dans la liste. La dernière
ligne ne fait que réécrire ce qui est déjà dans la liste. Pour faire la liste des
valeurs, il sut d'utiliser le for décrit ci-dessus.
Pour déterminer si un mot se trouve dans la liste, on écrit la ligne suivante :
if (liste["ccc"]) { "ccc" est dans la liste }
Si le mot se trouve dans la liste, liste[mot] vaut true, sinon liste[mot] est
indéni ce qui est équivalent à false.
2.17.5 Aectation de tableaux (doublons)
Lorsque l'on aecte un tableau déni dans une variable à une autre variable, on ne crée pas de nouveau tableau, mais deux variables qui voient le même tableau. On pourrait également considérer que l'on a un seul tableau
avec deux noms pour y accéder.
Dans l'exemple ci-dessous, on crée un tableau dans liste, puis on l'aecte
à la variable nouveau. On fait des changements par le biais de l'une ou l'autre
des variables et on peut constater que les valeurs changent également dans
le tableau vu de l'autre variable (gure 2.1).
var liste = ['bleu', 'rouge'] // valeurs initiales
38
r x et y
CHAPITRE 2.
Déclarer x et y
nouveau
liste
bleu
rouge
LANGAGE JAVASCRIPT
liste[0]
dans y
millisec dans y
nouveau
(2ème affectation)
her y
millisec dans x
noir
blanc
dans x
rx-y
5x
Afficher x - y
Figure 2.1 Doublons
var nouveau = liste
liste[ 0 ] = 'jaune'
nouveau[ 0 ] = 'gris'
nouveau = ['noir','blanc'] // désormais, liste et nouveau
// ne contiennent plus la même chose
Si l'on examine liste[0] juste après l'aectation de nouveau[0], on constate
que liste a changé en même temps que nouveau.
Les tableaux de caractères obéissent à la même règle quant à leur transfert
dans diérentes variables. La deuxième instruction ci-dessous ne crée pas de
nouvelle string. Par contre, la troisième instruction remet une nouvelle string
dans str1 et ne modie donc pas str2.
var str1 = 'aaa'
var str2 = str1
var str1 = 'bbb'
Aprés la deuxième instruction, il n'existe que la string 'aaa', mais après
la troisième, deux strings distinctes sont mémorisées.
Ce fonctionnement des tableaux et des strings ne pose en général pas de
problème et l'on a pas à trop s'en soucier, mais lorsqu'on a pensé obtenir une
nouvelle version d'un tableau au moyen d'une aectation, les comportements
paraissent étranges tant qu'on ne comprend pas d'où vient le problème.
Notez que ce problème ne survient pas que dans Javascript.
Pour créer une nouvelle version d'un tableau ou d'une string il faut
utiliser, par exemple, la fonction concat (), sans argument (voir paragraphe
2.18.4) :
nouveau = liste.concat()
str2 = str1.concat()
La ligne ci-dessus recrée un nouvelle string identique à la première. Il en
serait de même pour des tableaux.
2.17.
39
TABLEAUX
2.17.6 Transformation de tous les éléments
Il est possible de créer un nouveau tableau en appliquant une transformation à chaque élément d'un premier tableau. Dans l'exemple suivant, on
obtient un tableau dans lequel chaque élément est le double de l'élément
correspondant du premier tableau :
var premier = [1,2,3,4,5]
var res = premier.map(function(x) {return x*2})
Après cette instruction, res contient [2,4,6,8,10]. La fonction à appliquer
à chaque élément se dénit comme une fonction normale, mais sans nom et
on la place dans les parenthèses de map.
On peut également placer au même endroit le nom d'une fonction dénie
ailleurs, qui n'a qu'un seul paramètre et que retourne une valeur. Seul son
nom est placé entre les parenthèses, on ne doit pas ajouter les parenthèses.
Par exemple :
nbReels = [3.45, 2.18, 3.1415926]
entiers = nbReels.map(Math.round)
Math.round contient une dénition de fonction. Si l'on plaçait Math.round()
entre les parenthèses de map, cela voudrait dire que l'on passe à map la valeur
retournée par Math.round, pas la dénition de cette fonction !
2.17.7 Commentaire sur les syntaxes
Il y plusieurs moyens pour réaliser un certain nombre des opérations décrites dans les paragraphes précédents.
Tous les langages (y compris C++ et Java) n'ont pas ces multiples possibilités. En Javascript, un objet est géré par le moteur de Javascript exactement
de la même manière qu'un tableau associatif. C'est pourquoi les diérentes
syntaxes sont à l'intersection des tableaux et des objets (section 8).
Ainsi, les trois initialisations ci-dessous créent la même entité dans le
moteur de Javascript.
new Array()
[]
{}
Mais on ne peut utiliser la dernière que pour des tableaux associatifs.
Chacune des lignes marquées par (1) à la page 36, (2) à la page 36 et (3) à
la page 37 ne pourraient pas être remplacées par une autre syntaxe.
Les deux instructions ci-dessous ont exactement le même rôle, la seconde
tirant sa syntaxe du concept d'objet.
couleurDrapeau['croix']
couleurDrapeau.croix
A la ligne (3) de la page 37, il y a une variable dans les parenthèses carrées,
on ne pourrait donc pas utiliser la syntaxe de droite ci-dessus. On ne pourrait
40
CHAPITRE 2.
LANGAGE JAVASCRIPT
pas non plus l'utiliser dans le cas, déjà mentionné, où la clé contiendrait, par
exemple 'bord-drapeau', à cause du signe moins.
Ainsi, seuls quelques cas d'utilisation sont ambigus, les autres ont des
critères d'utilisation bien dénis.
2.18 Opérations sur les tableaux et les chaînes
2.18.1 Ordre inverse
Pour inverser l'ordre des éléments d'un tableau, il sut d'appeler la fonction reverse.
var liste = ['bleu', 'rouge'] // valeurs initiales
var nouveau = liste.reverse()
Attention, non seulement la variable nouveau contient le nouveau tableau,
mais le tableau contenu dans la variable liste a également changé et contient
aussi la liste triée après l'appel. Après l'appel, les deux variables ont donc
le même contenu. En général, on n'aecte pas le tableau trié à une nouvelle
variable, mais ce retour permet de mettre les fonctions en cascade :
var nouveau = liste.sort().reverse()
liste.sort() produit une nouvelle liste sur laquelle l'opération reverse est
appliquée.
2.18.2 Tri des éléments d'un tableau
Les éléments d'un tableau peuvent être triés selon diérents critères.
var nouveau = liste.sort()
L'ordre par défaut est l'ordre alphabétique, même pour les chires. Par
exemple les chires 100, 25, 40 seraient classés dans cet ordre, car le 1 (de
100) est placé avant le 2 (de 25) !
Pour faire un tri arithmétique, il faut dénir une fonction de comparaison
et la passer comme argument de sort. La fonction dénie ci-dessous retourne
une valeur négative si a < b. Elle crée donc un ordre croissant si on la passe
à sort.
var liste = [ 4, 100, 25 ] // valeurs initiales
function ordreNombre(a,b) {
return ab
}
liste.sort(ordreNombre)
On peut également écrire :
liste.sort( function (a,b) {return ab} )
push
pop
2.18.
shift
unshift
3
2
1
0
OPÉRATIONS SUR LES TABLEAUX ET LES CHAÎNES
x=
3
5
2
8
2
4
41
1
y = x.shift() Æ y == 3
x=
5
2
8
2
4
1
5
2
8
2
4
x.unshift(9)
x=
e avec fichier
1
y = x.pop() Æ y == 1
x=
Iframe éditable
9
9
5
2
8
2
4
5
2
8
2
4
x.push(7)
x=
9
7
Pied de page
Figure 2.2 Fonctions de décalage
en plaçant la dénition directement dans l'argument de l'appel à sort, sans
lui donner de nom en propre.
La fonction passée en argument est utilisée dans le code de la fonction
sort, caché dans le navigateur. Ce code fait appel à la fonction passée en
paramètre pour déterminer si deux éléments sont dans l'ordre ou s'il faut
échanger leurs places. Il y a donc dans ce code quelque chose proche de ce
qui est indiqué ci-dessous.
sort(fctCompare) {
. . . choisir deux éléments: e1 et e2 . . .
if (fctCompare(e1, e1) < 0) {
. . . les croiser . . .
}
. . . recommencer avec d'autres éléments . . .
}
Si l'on échange les variables a et b dans le retour de la fonction et place
(ba ), le code de sort établira donc automatiquement l'ordre décroissant. On
peut introduire toutes sortes d'ordres en modiant la fonction correspondante.
2.18.3 Décalages dans des tableaux
On peut utiliser un tableau comme un registre à décalage et introduire de
nouveaux éléments en bas ou en haut du tableau, ou extraire des éléments
des deux bouts et en décalant les autres éléments pour faire de la place au
42
CHAPITRE 2.
LANGAGE JAVASCRIPT
nouvel élément, respectivement boucher le trou laissé par l'élément extrait.
a gure 2.2 illustre l'eet de chacune des opérations.
casier.shift()
casier.unshift(9)
casier.pop()
casier.push(7)
extrait le premier élément du tableau
introduit un nouvel élément au début du tableau
extrait le dernier élément du tableau
ajoute 7 à la n du tableau
On pourra ainsi produire un registre FIFO (rst in, rst out), qui fonctionne comme un tapis roulant en ajoutant des éléments devant (unshift ) et
les enlevant derrière (pop ), ou un registre de type LIFO (last in, rst out), qui
fonctionne comme une pile d'assiettes en ajoutant des assiettes au sommet
de la pile (push ) et les reprenant du sommet également (pop ). Voir également
la gure 2.7, page 52.
Les éléments de ces tableaux peuvent être des nombres, des chaînes, voire
même des tableaux.
2.18.4 Fonction concaténation
L'instruction
nouveau = casier.concat(liste)
ajoute le contenu de la variable liste à la variable casier. Comme on l'a déjà
vu à la section 2.17.5, cette fonction ne modie pas le tableau sur lequel la
fonction a été exécutée et elle peut être utilisée sans argument pour créer une
vraie copie d'un tableau.
2.19 Fonctions mathématiques
Voici les principales fonctions mathématiques disponibles en Javascript.
Dans les fonctions trigonométriques, les angles sont exprimés en radians.
Pour mémoire, l'expression suivante transforme l'angle x de radians en degrés :
x * 180 / Math.PI
2.19.1 Fonctions trigonométriques
Math.sin(x)
Math.cos(x)
Math.tan(x)
Math.asin(x)
Math.acos(x)
sinus
cosinus
tangente
arc sinus
arc cosinus
2.20.
43
DATES
arc tangente de x où (−π/2 < x < π/2)
angle formé par la droite reliant l'origine au point
(x ;y) et l'axe x (−π/2 < angle < π/2)
Math.atan(x)
Math.atan2(y,x)
2.19.2 Fonctions exponentielles/logarithmiques
Math.exp(x)
Math.log(x)
Math.pow(x,y)
Math.sqrt(x)
ex
logarithme naturel (base e)
xy
racine carrée
2.19.3 Arrondis/comparaison/aléatoire
Math.round(x)
Math.ceil(x)
Math.floor(x)
Math.abs(x)
Math.max(x,y)
Math.min(x,y)
Math.random()
arrondi à l'entier le plus proche
arrondi à l'entier supérieur
arrondi à l'entier inférieur
valeur absolue (x>0)
maximum de x et y
minimum de x et y
nombre aléatoire a (0< a <1)
2.19.4 Constantes mathématiques
Math.E
Math.PI
Math.SQRT1_2
Math.SQRT2
e (∼ 2.718)
π
racine carrée de 1/2 (∼ 0.707)
racine carrée de 2 (∼ 1.414)
Pour d'autres constantes voir sur Internet ou un livre plus complet sur
Javascript.
2.20 Dates
L'objet Date permet de connaître l'heure et la date d'aujourd'hui et de
calculer des dates.
Pour acher la date et l'heure du moment, il sut d'exécuter l'instruction
suivante.
alert(Date())
L'instruction Date() retourne en eet la date et l'heure du moment où
elle est appelée.
44
CHAPITRE 2.
LANGAGE JAVASCRIPT
Pour obtenir les composants de la date, il faut utiliser les instructions
suivantes. Seules les fonctions les plus utiles sont décrites ci-dessous. Référezvous à la documentation standard pour en découvrir quelques autres. La
première ligne doit être exécutée une fois avant d'exécuter l'une des lignes
qui suivent. La date mémorisée dans laDate ne change pas avant qu'on appelle
new Date() à nouveau.
laDate = new Date() // mémorise la data et l'heure de l'instant où elle
est exécutée
laDate.getDate() // fournit le numéro du jour (1-31)
laDate.getMonth() // fournit le numéro du mois (0-11)
laDate.getFullYear() // fournit l'année (2009)
laDate.getDay() // fournit le jour de la semaine (0-6)
laDate.getHours() // fournit l'heure (0-23)
laDate.getMinutes() // fournit les minutes (0-59)
laDate.getSeconds() // fournit les secondes (0-59)
laDate.getMilliseconds() // fournit les millisecondes
Les fonctions suivantes permettent de faire des calculs sur les dates et
de les mémoriser de façon compacte. On pourrait également les utiliser pour
chronométrer des temps, mais il faut prendre garde au fait que l'ordinateur
fait de nombreuses choses en parallèle et ne peut garantir une grande précision. La fréquence est correcte, mais il peut arriver qu'il lise les millisecondes
un peu trop tard.
laDate.setTime(millisec) // introduit dans laDate la date correspondant à l'instant représenté par millisec après le début de l'année 1970
laDate.getTime() // fournit le nombre de millisecondes écoulées depuis
le début de l'année 1970
Attention, pour obtenir la valeur instantannée, il faut rééxecuter le premier appel ci-dessus : laDate = new Date().
2.21 Appels diérés ou périodiques
Il est possible de demander au navigateur d'appeler une fonction dans un
certain nombre de millisecondes ou de l'appeler périodiquement. Les paragraphes ci-dessous montrent diérentes façons de faire cela.
2.21.1 Appels de base
L'instruction ci-dessous demande au navigateur d'appeler la fonction fct
dans une seconde et demie (1500 millisecondes). La valeur retournée doit être
mémorisée si l'on veut plus tard annuler l'appel avant qu'il n'ait eu lieu.
2.21.
45
APPELS DIFFÉRÉS OU PÉRIODIQUES
setTimeout(action, 3000)
Planifié
fct1, 1200
action, 3000
exec 6000
exec,
Moteur du
navigateur
Figure 2.3 Exécution de setTimeout
timer = setTimeout(fct, 1500)
Pour annuler l'appel, on utilise :
clearTimeout(timer)
Si l'appel a déjà eu lieu, l'instruction ci-dessus n'a aucun eet.
2.21.2 Quelques détails du fonctionnement
Le navigateur garde la liste des fonctions qu'on lui a demandé d'appeler
dans une liste. Sur la gure 2.3, on suppose que les appels aux fonctions
fct1, action et exec a été préalablement planié dans 1200, 3000 et 6000ms 2
respectivement.
Considérons maintenant l'appel à setTimeout (gure 2.3). L'appel à setTimeout est bref, non bloquant. Il ne fait qu'inscrire le nom de la fonction
et l'heure à laquelle elle doit être appelée dans une table du navigateur. Puis
le code qui a appelé setTimeout est continué avant que la fonction planiée
soit exécutée, même si l'on écrit un délai de 0 dans setTimeout.
Si un appel à la fonction est déjà mémorisé dans la table des fonctions
planiées, un deuxième appel est inséré.
Il peut y avoir d'autres fonctions planiées dans la table. L'ordre d'exécution correspond à celui des dates planiées.
Voir la relation avec les événements au paragraphe 3.2.5.
2.21.3 Appel d'une instruction
Il est également possible de placer une chaîne contenant une instruction
en premier paramètre, comme illustré ci-dessous.
x=7
timer = setTimeout("alert(x)", 1500)
x=16
2. Le système doit évidemment décrémenter ces valeurs toutes les millisecondes
46
CHAPITRE 2.
LANGAGE JAVASCRIPT
Attention, cette instruction est donc appelée après que l'aectation x =
16 a été eectuée, c'est-à-dire que l'alerte montrera la valeur 16 et non 7. Le
même eet pourrait survenir si les variables globales sont utilisées ailleurs
dans le programme, ce qui pourrait créer des problèmes.
D'autre part, si x était une variable locale, elle n'existerait plus au moment
où l'alerte est exécutée.
Si l'on voulait utiliser la valeur de la valeur de x au moment de la mise
en mémoire de l'appel diéré, il faudrait écrire le code suivant,
timer = setTimeout("alert("+x+")", 1500)
de manière à créer la chaîne "alert(145)", en supposant que x vaut 145 au
moment de l'appel à setTimeout.
Il existe encore une autre forme pour passer des paramètres, mais elle est
plus complexe et est donnée à titre d'information.
2.21.4 Appel par fermeture
L'appel par fermeture est plus sophistiqué.
function englobante(nom){
var salutations = 'Salut '
setTimeout( function(){alert(salutations + nom)}, 1000)
}
Lorsque le programme appelle une fonction qui dénit une fonction interne comme celle qui est dénie ci-dessus dans le premier argument de setTimeout :
function(){alert(salutations + nom)}
il crée une entité appelée fermeture (closure), qui contient cette fonction,
ainsi que les arguments et variables locales de la fonction englobante. Ainsi,
lorsqu'elle est exécutée, la fonction alert() ci-dessus dispose des variables
locales (nom et salutations ) avec les valeurs qu'elles avaient à l'appel de
setTimeout.
Notez que si l'on crée plusieurs fonctions à l'intérieur de la même fonction,
celles-ci partagent les mêmes variables locales.
Attention, les variables globales (section 2.14.2), quant à elles, peuvent
changer avant l'exécution de la fonction programmée, comme mentionné au
paragraphe précédent.
Voir également la section 8.5.
2.21.5 Appels périodiques
Les fonctions
timer = setInterval (fct, 1200)
2.22.
47
EXERCICES
Déclarer x et y
Déclarer x et y
nouveau
liste
ble
ro
millisec dans y
millisec dans y
nouveau
(2ème affectation)
Afficher y
millisec dans x
millisec dans x
5x
Afficher x - y
Afficher x - y
Figure 2.4 Schéma du programme du chronomètre
clearInterval(timer)
sont semblables aux précédentes. La première ligne provoque l'appel périodique (toutes les 1200 ms) de la fonction. La seconde ligne élimine la demande
périodique.
2.22 Exercices
Pour faire les exercices ci-dessous, il faut entrer des valeurs et en acher
d'autres. Pour acher la valeur de x, on écrit alert(x) et pour demander
une valeur à l'utilisateur, on écrit y = prompt("Donnez une valeur"). Ces
fonctions sont expliquées au paragraphe 3.1.
2.22.1 ex - Chronomètre
Construisez diérentes versions de chronomètres approximatifs. Pour faire
ce chronomètre, on va enregistrer le nombre de millisecondes écoulées depuis 1970, au moment où on lance le programme, puis présenter une fenêtre
d'alerte. Quand l'utilisateur clique cette fenêtre, le programme relit le nombre
courant de millisecondes et ache la diérence d'avec la valeur lue au début
du programme. La première version est illustrée sur la gauche de la gure 2.4.
1. Créez la page HTML ci-dessous en utilisant le bouton html, à droite de
la fenêtre d'édition des chiers HTML, puis le bouton script, juste en
no
bl
48
CHAPITRE 2.
LANGAGE JAVASCRIPT
dessous.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;
charset=utf-8"/>
<title>nomDeFichier</title>
<script type="text/javascript">
// placement de votre code
</script>
</head>
<body>
</body>
</html>
2. Dénissez une variable y initialisée au nombre de millisecondes écoulées
depuis 1970 (voir paragraphe 2.20) entre les balises <script>.
3. Faites acher le nombre de millisecondes depuis 1970.
4. Quand l'utilisateur clique l'alerte, le programme doit relire le nombre de
millisecondes et acher la diérence entre les deux valeurs. Attention,
il faut écrire une nouvelle fois new Date(), pour obtenir le nouveau
nombre de millisecondes.
Pour qu'on puisse utiliser le chronomètre plusieurs fois de suite, mettez les
alertes dans une boucle, selon le schéma sur la partie droite de la gure 2.4.
1. Dénissez une boucle qui s'exécute quelques fois. Pour les premiers
essais, faites une boucle qui ne fait que quelques tours, sinon vous risqueriez de ne plus en sortir 3 .
2. Dans la boucle, lisez le nombre de millisecondes écoulées depuis 1970
et aectez-le à x.
3. Toujours dans la boucle, codez un appel à alert dans lequel vous introduisez la diérence entre les deux variables.
4. Lancez le programme et voyez le nombre de millisecondes augmenter à
chaque clic.
Achez le temps écoulé entre deux clics :
7. Après alert, copiez la valeur de x dans y pour le calcul du prochain
tour.
3. Si cela arrivait, il faut tuer le programme Firefox ! (voir section 2.8)
2.22.
49
EXERCICES
Déclarer x et y
boucle
Tirer un nombre au hasard
1
millisec dans y
Demander un nombre
millisec dans x
Afficher « Trop petit »
Afficher x - y
Afficher « Trop grand »
2
length-1
[1, 3, 3, 1]
[1] 4
avant
la boucle
Afficher « Bravo »
Figure 2.5 Schéma du programme de devinette
Chaque fois que vous cliquerez, vous verrez le temps écoulé depuis le
dernier clic.
Comme troisième partie, vous pouvez ajouter la date dans alert.
2.22.2 ex - Devinette
On vous propose de créer un programme qui fait deviner un nombre dans
les balises <script>. Le programme demande une valeur à l'utilisateur, puis
lui indique si la valeur est trop grande ou trop petite, et répète ces instructions
jusqu'à ce que l'utilisateur trouve la bonne réponse.
1. Créer un nombre aléatoire entre 0 et 10 au moyen de l'instruction
var aDeviner = Math.floor(Math.random()*10) + 1
2. Créer une boucle qui demande un nombre entre 1 et 10 à l'utilisateur,
qui teste si le nombre entré est plus grand, égal ou plus petit que le
nombre tiré au hasard, puis redemander un nouveau nombre si l'utilisateur ne l'a pas trouvé.
Vous pouvez améliorer le programme en achant l'indication Trop petit ou Trop grand directement dans la fenêtre prompt.
6
4
1
après
la boucle
50
CHAPITRE 2.
boucle
Tirer un nombre au hasard
1
Demander un nombre
Afficher « Trop petit »
LANGAGE JAVASCRIPT
2
length-1
[1, 3, 3, 1]
[1] 4
6
4
1
Afficher « Trop grand »
avant
la boucle
Afficher « Bravo »
après
la boucle
Figure 2.6 Schéma du programme du triangle
2.22.3 ex* - Triangle de Pascal
Acher dans une séquence de fenêtres d'alerte les lignes successives du
triangle de Pascal.
1
11
121
1331
14641
Dans une première étape, introduire dans les balises <script> le code qui
calcule la ligne qui suit la ligne [1 3 3 1] et qui dépose les nombres de cette
ligne dans un deuxième tableau (voir gure 2.6) :
1. Créer un nouveau tableau vide [1], puis coder une boucle qui parcourt
le tableau de la ligne précédente, [1 3 3 1], depuis 1 (pas 0) jusqu'à la
n et dépose la somme de l'élément pointé par la boucle et de l'élément
précédent, à l'index de la boucle dans ce nouveau tableau.
2. Après la boucle, introduire 1 à l'index correspondant à la longueur du
tableau précédent.
3. Introduire la variable du nouveau tableau dans la fonction alert() pour
voir le résultat.
4. Pour créer le tableau complet, initialiser le premier tableau à [1] et
englober cette première boucle et l'alerte dans une deuxième boucle.
2.22.
51
EXERCICES
Après l'alerte, dans la boucle, on copie le tableau qui contient la ligne
suivante dans le tableau qui contient la ligne précédente de sorte que
la boucle traite les lignes successives.
Attention, vous risquez de tomber dans un piège (voir section 2.17.5).
Dans le premier bloc ci-dessous, la variable du tour précédent et celle du
tour suivant devient la même après l'aectation de suivant à précédent, ce
qui brouille naturellement les calculs.
// problématique
precedent = [ ]
suivant = [ ]
boucle
suivant[0] = 1 // modication de suivant
calcul de suivant à partir de precedent
precedent = suivant // suivant et précédent ont les mêmes contenus
// solution 1
precedent = [ ]
suivant = [ ]
boucle
suivant[0] = 1
calcul de suivant
precedent = suivant.concat()
// solution 2
precedent = [ ]
boucle
suivant = [1] // création
calcul de suivant
precedent = suivant
Les colonnes ci-dessus montrent deux possibilités de pallier cet eet. Dans
la colonne de gauche, on utilise concat pour recréer un nouveau tableau indépendant de suivant. Dans la colonne de droite, on recrée un nouveau tableau
suivant à chaque nouveau tour de boucle.
2.22.4 ex - Décalages dans un tableau
Créez un programme qui propose à un utilisateur d'eectuer des push ou
des pop dans un tableau et qui ache le résultat après chaque opération.
Pour demander à l'utilisateur le texte qu'il veut entrer dans push, on peut
utiliser la commande prompt (gure 2.7) et lui faire acher en même temps
la valeur courante du tableau :
var entree = prompt(tbl)
Dans l'argument de prompt, on place habituellement un texte qui indique
ce que l'utilisateur doit entrer, mais ici on propose de placer directement le
tableau sur lequel on opère les push ou les pop. Le système sait l'acher de
52
CHAPITRE 2.
LANGAGE JAVASCRIPT
push
pop
shift
unshift
3
2
1
0
Figure 2.7 Piles d'assiettes
x=
Iframe avec fichier
Pied de page
5
2
8
2
4
1
façon compréhensible par un humain. On verra ainsi automatiquement les
y = x.shift() Æ y == 3
eets des opérations.
Lorsqu'on lui demande l'opération
x = 5qu'il 2veut 8voir2exécuter,
4
1l'utilisateur
entrera les deux premières lettres de l'opération demandée, suivies d'un mot
ou d'une lettre à insérer dans lex.unshift(9)
tableau tbl.
Pour détecter l'opération demandée, on peut faire un branchement sur
x = (parmi
9 5les premières,
2
8 il2 y a4deux1 fois la
la deuxième lettre de la commande
lettre p) :
y = x.pop() Æ y == 1
switch (entree.charAt(1)) { // voir paragraphe 2.16.5
case "u": . . .
Iframe éditable
3
x=
9
5
2
8
2
4
1. Dans les balises <script> x.push(7)
introduisez la dénition d'un tableau vide.
2. Créez un boucle qui tourne quelques fois et placez-y la demande de
2
8 2 4
7
nombre et d'opération. x = 9 5
3. Ajoutez un switch, avec dans chaque cas duquel une opération à eectuer. N'oubliez pas les break.
Vous pouvez également ajouter les opérations shift et unshift sur le même
tableau et voir ainsi l'eet des 4 opérateurs.
Vous pouvez encore ajouter les opérations suivantes. Si le tableau devient
plus long que 4, par exemple, après un push, éliminez le premier élément de
l'autre extrémité. De même pour le unshift.
Vous pouvez acher dans une alerte la valeur retirée à l'autre bout.
12 août 2010
Chapitre 3
Interactions HTML - Javascript
3.1 Achage - entrée de données
3.1.1 Fenêtres surgissantes
alert
Le moyen le plus simple de communiquer quelque chose à l'utilisateur est
l'instruction alert(expression). Cette fonction ouvre une petite fenêtre sur
la page et y ache l'expression, le plus souvent une concaténation de chaînes
et de variables.
prompt
Pour lire une string, il sut d'introduire l'instruction ci-dessous :
var txt = prompt("Entrez un nom, s'il vous plaît")
Lorsque l'instruction s'exécutera, une fenêtre surgissante (pop-up) s'ouvrira et achera le texte placé entre parenthèses (qui peut être déni soit
par une constante entre guillemets, soit dans une variable) et un champ dans
lequel on peut entrer le texte demandé.
conrm
L'instruction confirm("texte") ouvre une fenêtre surgissante qui ache
le texte entre parenthèses. Cette fenêtre possède deux boutons : OK et Annuler. Cela permet de demander à l'utilisateur s'il conrme une action qu'il
a déclenchée. Cette fonction s'utilise donc généralement dans un if.
53
54
CHAPITRE 3.
INTERACTIONS HTML - JAVASCRIPT
3.1.2 Lecture d'un nombre
prompt retourne toujours une chaîne de caractères. Pour obtenir un nombre,
il faut utiliser
var n= parseInt(prompt("demande"))
parseInt(chaîne) convertit la chaîne de caractères placés entre ses pa-
renthèses en un nombre entier. S'il y a des lettres après les chires, les lettres
sont ignorées. Si l'argument ne contient que des lettres, ou si la chaîne est
vide, cette fonction retourne le symbole NaN, qui signie Not a Number .
parseInt(chaîne,8)
considère que la chaîne représente un nombre
exprimé dans la base 8. En base 16, il faut compléter les chires en dessus
de 9 par a, b, c d et f.
La chaîne "012" est considérée comme exprimant un nombre en octal,
parce qu'elle commence par 0, et vaut donc 1010 . La chaîne "09" est donc
invalide, car 9 n'est pas un chire octal. Pour convertir un nombre demandé
à un utilisateur, il vaut donc mieux écrire parseInt(nbre, 10), pour éviter une
surprise.
3.1.3 Fonction eval
La fonction eval permet d'exécuter des programmes créés en ligne, c'està-dire après que la page qui contient le programme ait été chargée. La chaîne
passée ci-dessous à eval aurait pu être entrée par l'utilisateur dans la fonction
prompt, par exemple.
alert("sin(30) = " + eval("Math.sin(30 * Math.PI / 180)"))
Cette ligne ache : sin(30) = 0.499999999
S'il y a des erreurs dans le code fourni, la fonction l'annonce. Pour détecter
ces erreurs, il faut placer la fonction eval dans les instructions suivantes :
try {
alert("sin(30) = " + eval("Math.sin(30 * Math.PI / 180)" ))
} catch (e) {
alert("Erreur "+ e.message)
}
3.2 Gestion des éléments HTML
3.2.1 Repérage des éléments HTML
Les instructions Javascript permettent d'accéder aux éléments et de les
modier sans recharger la page. Par exemple, pour modier la largeur de
3.2.
GESTION DES ÉLÉMENTS HTML
55
l'élément ci-dessous, qui possède l'attribut id='monImage',
<img src='bille1.gif' id='monImage'>
on utilise l'appel
document.getElementById("monImage").width = 100+"px"
L'élément adressé est celui dont la valeur d'attribut id se retrouve dans
l'argument de getElementById(). Après le point qui suit cette fonction, on
place le nom de l'attribut de l'objet qu'on veut modier.
La valeur placée à droite du signe = doit correspondre à une chaîne contenant un nombre suivi d'une unité (px, mm, cm). Cette chaîne peut bien évidemment être composée par concaténation. Ainsi toutes les chaînes dénies
ci-dessous sont valables et expriment la même valeur.
"100px"
100+'px'
x = 100; x+"px"
'10'+'0px'
(50+50)+"px"
On peut en particulier modier les attributs placés dans le style, qui
s'adressent logiquement :
document.getElementById("monImage").style.left = "100px"
3.2.2 Evénements
Il est possible de programmer les éléments HTML pour que lorsqu'on le
clique, des instructions Javascript soient exécutées.
<button onclick='instructions'>Clic</button>
En général, on place à cet endroit un appel à une fonction. Il arrive souvent
que l'on doive introduire une chaîne dans l'appel. Il faut donc utiliser les deux
types de guillemets, comme ci-dessous :
<button onclick='appelFct("chaine")'>Clic</button>
Les types d'événements suivants sont dénis dans une page HTML.
onclick, onmousedown, onmouseup l'événement est généré lorsque l'on presse
une touche du clavier alors que l'élément a été sélectionné par la souris (presser + relâcher), respectivement quand on presse la souris sur
l'événement ou quand on la relâche.
onkeypress l'événement est généré quand on presse une touche du clavier
et que l'élément qui contient onkeypress est sélectionné. On l'utilise
souvent pour un input mais si on le met dans body, on peut récupérer
toutes les pressions sur les touches, par exemple pour des jeux.
onload, onunload l'événement est généré quand la page est chargée, respectivement quittée. Les instructions placées dans les balises <script>, en
56
CHAPITRE 3.
INTERACTIONS HTML - JAVASCRIPT
dehors de toute fonction, par exemple les initialisations de variables
sont exécutées avant que les éléments de la pages soient dessinés. Si on
veut les modier par programme juste après qu'ils aient été créés, il
faut les mettre dans une fonction appelée depuis l'attribut onload placé
dans la balise <body>.
Les attributs déclenchant des événements peuvent être placés dans la
majorité des éléments, même dans la balise <table> ou dans une des balises
<tr> ou <td>.
3.2.3 Symbole this
A la ligne suivante, l'appel contient this. Ce symbole représente l'élément
dans lequel il apparaît.
<span id="ID12" onclick="affiche(this)"></span>
function affiche(unElement) {
unElement.innerHTML = "<b>"+nombre+"</b>"
}
Dans la fonction, unElement reçoit donc l'élément d'où part l'appel. this
est donc équivalent à document.getElementById('ID12') en notant que ID12
est l'identicateur de l'élément contenant l'appel.
On peut également mettre un argument de this dans l'appel :
<input id="ID13" onkeypress="affiche(this.value)">
function affiche(uneValeur) {
alert(uneValeur)
}
3.2.4 L'objet event
Pour transmettre au programme les détails de l'événement, le navigateur
gère des objets (event) qui permettent de déterminer les conditions dans
lequelles un élément a été cliqué (position sur la fenêtre, touche majuscule,
etc). Le concept d'objet sera vu au chapitre 8, mais pour l'instant il nous
sura de savoir que la valeur de l'attribut d'un objet est obtenue en faisant
suivre l'objet event par un point puis par l'attribut recherché.
Pour avoir accès à l'objet event, il faut dénir l'argument event parmi
les arguments de l'appel, récupérer cet argument formel dans un argument
eectif, évidemment placé à la même position, comme ci-dessous. Le nom
eectif est toutefois arbitraire.
<button onclick='gerer(this, event)'>Clic< /button>
3.2.
GESTION DES ÉLÉMENTS HTML
57
function gerer(elem, event) {
alert(event.clientX)
if (event.shiftKey) {alert("Touche majsucule enfoncée")}
}
Les principaux attributs ci-dessous sont disponibles (pour toutes les possibilités, voir Internet, car les arguments dépendent des navigateurs) :
event.clientX, event.clientY les coordonnées de l'endroit où l'utilisateur
a cliqué, depuis la gauche de la page HTML dans laquelle l'utilisateur
a cliqué
event.pageX, event.pageY les coordonnées de l'endroit où l'utilisateur a
cliqué, depuis la gauche de la fenêtre dans laquelle l'utilisateur a cliqué,
c'est-à-dire la partie visible de la page.
event.ctrlKey, event.shiftKey attribut vrai si la touche contrôle était
enfoncée au moment du clic, attribut vrai si la touche majuscule était
pressée
event.charCode, event.keyCode code de la touche pressée (par exemple
pour l'élément input du paragraphe 3.4), code de la touche pressée si
elle n'est pas un caractère, mais par exemple un retour de ligne ou une
èche de direction. Pour ces deux attributs, le plus simple est de créer
un petit programme qui ache les valeurs de ces codes pour déterminer
dans quel attribut le moteur du navigateur met quelle valeur.
3.2.5 Traitement multiple des événements
Si un événement est placé à deux niveaux d'éléments ou plus, par exemple
dans <table> et <td> ou dans un <h1> et dans un<span> contenu dans le
<h1>, la méthode sera appelée deux fois. Si l'on veut empêcher cela, il faut
appeler la méthode suivante dans le dernier code dans lequel on veut traiter
l'événement :
event.stopPropagation();
Certains événements sont traités par le navigateur, par exemple les positionnements du curseur sur la page, la touche delete pour revenir à la page
précédente ou les caractères du clavier dans les zones d'éditions (text area,
inpur, iframe). Si ces événements sont également dénis dans l'attribut d'un
ou plusieurs éléments, les traitements de Javascript sont faits avant les traitements par le navigateur. Pour empêcher le navigateur de recevoir l'événement, on utilise l'instruction suivante :
event.preventDefault()
S'il y a un setTimeout dans une fonction d'événement, la fonction planiée
par cet appel est entrée dans la table du navigateur, puis le navigateur traite
58
CHAPITRE 3.
INTERACTIONS HTML - JAVASCRIPT
l'événement et nalement la fonction planiée est exécutée, après le délai
demandé. Cet ordre est respecté, même si le délai demandé est 0.
Par rapport à un appel direct de la fonction planiée, un appel indirect au
moyen de setTimeout reporte donc l'exécution de la fonction planiée après
le traitement de l'événement par le navigateur. Ainsi le dernier caractère
tapé sera donc visible dans document.getElementById("champInput").value,
ce qui n'est pas le cas dans un appel direct.
3.2.6 Introduction de code HTML, innerHTML
Dans l'exemple ci-dessous, l'entité placée entre les deux balises, comme
titre ou <span>Titre< /span>, est appelé le corps de la balise.
<h1>Titre< /h1>
<h1><span>Titre< /span>< /h1>
Javascript permet de remplacer le corps d'une balise ou d'en introduire
un en utilisant innerHTML, comme ci-dessous.
<td id="ID1_2" onclick="affiche(122)"></td>
function affiche(nombre) {
document.getElementById("monElement").innerHTML
= "<b>"+nombre+"</b>"
}
unElement.innerHTML introduit le texte qui se trouve à droite du signe =
dans le corps de l'élément dont l'identicateur est monElement. Le texte
introduit contient la balise <b> suivie du nombre (qui sera transformé en
string par la concaténation). Cette opération a le même résultat que si l'on
avait écrit <td><b>122</b></td> dans la page, en supposant que nombre
contenait 122 lors de l'appel.
Cette possibilité permet d'acher des résultats qui restent sur la page,
au contraire de ce qu'alert() faisait.
Notez qu'on utilise souvent <span> (dans une ligne) ou <div> (bloc entre
paragraphes) pour composer dynamiquement des parties de pages.
3.3 Gestion des cellules d'une table HTML
Dans cette section, nous allons prendre l'exemple d'une table dont on
veut gérer les cellules et les clics qu'elles subissent pour illustrer quelques
aspects de la gestion des éléments généralisables à de nombreuses situations.
On protera également de l'occasion pour parler de structure de programmes.
3.3.
GESTION DES CELLULES D'UNE TABLE HTML
59
La table ci-dessous a 2 × 2 cellules qui appellent diérentes fonctions. On
désire inverser les couleurs des cellules cliquées, les passant de bleu à blanc
ou inversément à chaque clic.
Chaque cellule montre une possibilité d'appel, mais dans une réalisation
concrète, on choisira bien évidemment une seule possibilité.
<table>
<tr>
<td id="0_0"
<td id="0_1"
< /tr>
<tr>
<td id="1_0"
<td id="1_1"
< /tr>
< /table>
onclick="oper0(0,0)">aaa< /td>
onclick="oper1('0_1')"'>bbb< /td>
onclick="oper1(this.id)">aaa< /td>
onclick="oper2(this)">bbb< /td>
Le code ci-dessous montre comment créer un tableau Javascript à deux
dimensions dont chaque élément reète l'état de la cellule correspondante du
tableau HTML
var tbl = new Array()
var n=3, m=3;
for (var i=0, i<n; i++) {
tbl[i] = new Array()
}
for (var j=0; j<m; j++) {
tbl[i][j] = 0;
}
Le tableau mémorisera 0 si la cellule est blanche et 1 si la cellule est bleue.
Les fonctions ci-dessous présentent une structure (on dira architecture
pour un problème plus complexe) qui concentre les diérentes aspects du
problème dans des fonctions distinctes.
La fonction calculer n'eectue que des calculs et laisse ses résultats dans
le bableau tbl. Cette fonction ne connaît rien de l'achage.
La fonction acher lit le bableau tbl et reporte l'état sur la table HTML.
Les fonctions oper traduisent les positions pour le calcul et appellent le
calcul et l'achage. On voit que le calcul et l'achage sont identiques pour
les 3 sortes de liaisons entre HTML et Javascript.
60
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
CHAPITRE 3.
INTERACTIONS HTML - JAVASCRIPT
// 3 possibilités d'interface élément-programme
function oper0( ix, iy ) { // passage de deux nombres
calculer(ix, iy)
afficher()
}
function oper1( cellId ) { // passage d'un id "5_2"
var t = cellId.split("_")
calculer(parseInt(t[0]), parseInt(t[1]))
afficher()
}
function oper2( cellAddr ) { // passage de l'élément-même
var t = cellAddr.id.split("_")
calculer(parseInt(t[0]), parseInt(t[1]))
afficher()
}
function calculer(ix, iy) {
// calculs
tbl[ix][iy] = 1 - tbl[ix][iy] // 1 ↔ 0
}
function afficher() {
// affichage
for (var i=0; i<n; i++) {
for (var j=0; j<m; j++) {
if (tbl[i][j] == 0) {
document.getElementById(i+"_"+j)
.style.backgroundColor = "white"
} else {
document.getElementById(i+"_"+j)
.style.backgroundColor = "blue"
}
}
}
}
La fonction oper0, appelée par la première cellule, reçoit en arguments
deux nombres qui identient la cellule par ses coordonnées x, et y dans la
table HTML ou dans le tableau tbl. Dans la fonction ci-dessus (ligne 2), il
sut d'utiliser directement ces valeurs pour indexer le tableau.
3.3.
GESTION DES CELLULES D'UNE TABLE HTML
61
La deuxième cellule passe son identicateur en argument plutôt que des
nombres. La ligne 6 retire les coordonnées de la cellule cliquée à partir de
son identicateur et retourne les deux coordonnées dans un tableau de deux
chaînes. Il faut donc utiliser parseInt pour les transformer en deux nombres
(ligne 7).
En utilisant les deux approches précédentes, il faut entrer deux fois les
coordonnées dans les cellules, ce qui soulève des risques d'erreurs.
Dans les deux dernières lignes du tableau, l'identicateur n'apparaît
qu'une fois, grâce à l'utilisation de this (paragraphe 3.2.3.
Dans la quatrième cellule, l'appel se trouve dans la balise <td> d'identicateur 1_1. this est donc équivalent à document.getElementById('1_1'). Et
donc dans oper2, cellAddr pointe sur la cellule du tableau qui a été cliquée. On
peut donc retrouver son identicateur en utilisant l'instruction cellAddr.id de
la ligne 11.
La troisième cellule utilise cette dernière instruction dans son appel, ce
qui lui permet d'appeler la même fonction que celle qui est appelée de la
deuxième cellule.
La ligne 16 transforme le 0 en 1 et vice-versa. Finalement, la double
boucle placée sur les lignes 20 à 27 ache toutes les cellules. L'identicateur
de chaque cellule est recomposé à partir des coordonnées par concaténation.
Pour l'achage, on repeint toutes les cellules, ce qui demande plus de
temps à l'ordinateur que si on ne modiait que la cellule dont la couleur a
changé, mais cela permet de découpler l'achage du reste. Si l'on voulait
faire un jeu (voir paragraphe 3.6.5), l'achage ne changerait pratiquement
pas.
On pourrait toujours optimiser le programme lorsqu'il a été testé pour
mémoriser l'ancienne position des cellules et ne changer que celles qui ont
changé. En introduisant l'achage dans le calcul, le programme deviendrait
plus complexe et on y introduirait plus facilement des erreurs lors du développement. Ces erreurs seraient plus diciles à trouver que dans une structure
organisée et le temps de développement du programme risquerait d'exploser.
3.3.1 Tableaux et index des cellules
Il existe un moyen permettant d'accéder directement aux rangées et aux
colonnes des cellules d'un tableau. Dans le code ci-dessous, on accède à la
colonne 2 de la rangée 1 d'une table :
document.getElementById("myTable").rows[1].cells[2]
Cette instruction retourne un élément de type <td>, sans être dérangée par
les espaces ou les retours de lignes.
62
CHAPITRE 3.
INTERACTIONS HTML - JAVASCRIPT
Si l'on a cliqué sur une cellule, on peut obtenir l'index de la cellule grâce à
l'attribut cellindex de cet élément. Pour une rangée, l'attribut correspondant
s'appelle rowindex. Ce dernier se retrouve à partir de la cellule au moyen de
this.parentNode.rowindex.
3.4 Liste d'éléments actifs - input
Dans cette section, nous allons voir un certain nombre d'éléments qui
permettent de lire les indications de l'utilisateur 1 .
Pour chaque élément, nous décrivons la façon de lire les informations
transmises par l'élément. Tous ces éléments peuvent recevoir des attributs
d'événement.
3.4.1 Champ de texte - text
Cette section présente la façon dont on peut lire des données à partir d'un
champ intégré dans une page HTML. L'instruction ci-dessous dessinera un
champ dans lequel l'utilisateur peut entrer des données sur une page HTML.
<input type="text" id="inputElem" value="initial"/ >
l'attribut value décrit les caractères qui apparaîtront dans le champ au chargement de la page. Pour lire le champ, on utilise simplement :
document.getElementById("inputElem").value
Si on tape un retour de ligne (dont le code est 13) dans l'élément cidessous, le navigateur appellera la fonction execute et passera la string que
l'utilisateur a entrée.
<input onkeypress='if(event.keyCode==13) execute(this.value)'
/>
3.4.2 Zone de texte - textarea
Il est possible de créer une zone qui fonctionne comme l'input, mais qui
peut contenir plusieurs lignes :
<textarea id="txtAreaElem" rows="8" cols="80">
texte initial
< /textarea>
Cet élément dessine un rectangle dans lequel on peut entrer des textes.
1. Ces éléments sont normalement encadrés dans une balise <form>, mais comme cette
dernière n'est utile que lorsque l'on envoie les informations au serveur, nous décrirons ces
éléments sans cette balise
3.4.
LISTE D'ÉLÉMENTS ACTIFS -
INPUT
63
Pour lire le texte, on utilise le même attribut que les input :
document.getElementById("txtAreaElem").value
3.4.3 Fenêtre éditable - iframe
La fenêtre iframe a toutes sortes de possibilités. On peut l'utiliser pour
créer des pages contenant des sous-pages statiques ou éditables, disposées les
unes par rapport aux autres dans des tables. La commande ci-dessous crée
une fenêtre statique dans une page.
<iframe src="entete.html" width="100%" height="50"
frameborder="0" scrolling="no">
< /iframe>
Le chier dont le lien apparaît dans l'attribut src est chargé automatiquement
dans la fenêtre iframe. frameborder détermine s'il y a un bord (1) autour de la
fenêtre ou non (0). scrolling spécie s'il faut mettre un ascenseur pour scruter
la fenêtre. Les options sont yes, no et auto, ce dernier faisant apparaître
l'ascenceur dès que le contenu devient plus long que la page.
Pour qu'une fenêtre iframe soit éditable, il faut appeler la fonction rendEditable dénie ci-dessous après que la fenêtre a été construite sur la page. Ceci
se fait en appelant la fonction depuis l'attribut onload de la balise <body>.
En général, on ne met pas d'attribut src dans l'iframe dans ce cas.
function rendEditable() {
document.getElementById('iframe_edit')
.contentWindow.document.designMode = 'on'
}
Pour obtenir le contenu d'une page éditable, on utilise l'une des fonctions
ci-dessous. La première fournit le texte sans les balises, la deuxième y inclut
les balises.
var txt = document.getElementById('iframe_edit')
.contentWindow.document.body.textContent
var txt = document.getElementById('iframe_edit')
.contentWindow.document.body.innerHTML
Pour introduire un texte dans la fenêtre éditable, on utilise la commande
suivante :
document.getElementById('iframe_edit')
.contentWindow.document.body.innerHTML = txt
Si l'on a introduit un chier au moyen de l'attribut src ou si l'on a introduit des balises au moyen de l'attribut innerHTML déni ci-dessus et qu'on
relit ensuite la page en utilisant ce même attribut, on obtient le texte initial.
64
CHAPITRE 3.
INTERACTIONS HTML - JAVASCRIPT
Par contre si on utilise textContent, les balises disparaissent. En lisant avec
textContent et en réintroduisant le texte obtenu avec innerHTML le texte
sera donc épuré des balises et apparaîtra normal dans l'écriture de base.
Pour introduire un texte contenant des balises et les faire apparaître à
l'utilisateur, il faut insérer des éléments text dans l'iframe en utilisant les
instructions suivantes (voir explications paragraphes 5.3.3 et 5.3.3).
var docIframe = document.getElementById('iframe_edit')
.contentWindow.document
docIframe.body.appendChild(docIframe.createTextNode(txt))
On peut s'amuser à lire le texte avec balises et l'acher en texte, plusieurs
fois de suite, puis faire le chemin en sens inverse, on verra comment le système
code les balises pour qu'elles apparaissent en clair.
3.4.4 Bouton - button
Le bouton ne transmet pas d'information. Il n'est utilisé que pour activer
un événement :
<input type="button" value="Nom"/ >
L'attribut value dénit le symbole qui apparaît sur le bouton.
3.4.5 Case à cocher - checkbox
Une case à cocher est présentée ci-dessous :
<input type="checkbox" id="checkboxElem"/ >
Pour tester si la case est cochée, on utilise l'instruction ci-dessous :
if (document.getElementById("checkboxElem").checked) {
alert("cochee")
} else {
alert("pas cochee")
}
3.4.6 Bouton radio - radio
Les boutons radio sont utilisés par groupes. Comme sur les anciennes
radios qui avaient une rangée de boutons dont un seul pouvait être enfoncé
à un moment donné, les autres ressortant lorsqu'on en pressait un nouveau,
les boutons radio remettent les autres à zéro lorsqu'on en clique un nouveau.
Les boutons appartenant à un groupe ont tous le même nom. Deux boutons sont dénis ci-dessous :
<input type="radio" name="rangeeA" id="radioNo1"/ >
<input type="radio" name="rangeeA" id="radioNo2"/ >
3.5.
GESTION DES IMAGES
65
Pour tester quel bouton est pressé, on utilise le même mécanisme que pour
les éléments précédents :
if (document.getElementById("radioNo1").checked) {
alert("cochee")
} else {
alert("pas cochee")
}
3.4.7 Sélection, simple ou multiple - select
Il est nalement possible de préparer des menus, dont on peut sélectionner soit une seule entité parmi plusieurs, soit plusieurs. La forme multiple
apparaît sous la forme d'un menu déroulant.
<select multiple id="multiSelectElem">
<option>HHH< /option>
<option>JJJ< /option>
< /select>
Pour déterminer les options qui sont cochées, on utilise :
document.getElementById("multiSelectElem").options[0].selected
Pour créer une sélection simple, il sut de laisser tomber le symbole
multiple de la balise principale.
3.5 Gestion des images
Comme expliqué au paragraphe 1.2.7, les images peuvent être insérées
dans une page au moyen de la commande <img src="URL" id="ID"/>.
Lorsque l'on veut changer l'image achée par cette commande, il sut d'exécuter le code suivant :
document.getElementById("ID").src = "newURL"
Cependant, lorsque l'on veut faire des animations il vaut mieux utiliser
le procédé suivant, pour avoir un meilleur contrôle sur le chargement des
images.
listeImages = []
listeImages[0] = new Image()
listeImages[0].onload = fonctionSuite // optionnel
listeImages[0].src = "URL1"
listeImages[1] = new Image()
listeImages[1].onload = fonctionSuite // optionnel
listeImages[1].src = "URL2"
66
CHAPITRE 3.
INTERACTIONS HTML - JAVASCRIPT
// éventuellement mettre la ligne suivante dans fonctionSuite
document.getElementById("ID").src = listeImages[1].src
Ainsi les images sont chargées une seule fois, même si elles sont achées
plusieurs fois.
L'instruction listeImages[0].onload = fonctionSuite permet de ne pas utiliser les images avant qu'elles soient complétement chargées. La fonction fonctionSuite, dénie par le développeur, est appelée quand l'image est complétement chargée. Si l'on doit acher des images par programme au chargement
de la page, il faut exécuter le code ci-dessus, mais coder un return après les
premières instructions. La dernière ligne ou d'autres lignes qui utiliseraient
les images doivent être placées dans la fonction fonctionSuite.
Cette fonction est appelée par toutes les images. Elle doit donc compter
le nombre de fois qu'elle est appelée et eectuer un retour tant qu'elle n'a pas
été appelée le nombre de fois correspondant au nombre d'image préchargées.
3.6 Exercices
3.6.1 ex - Calcul de moyenne
Entrez une série de nombres dans une boucle et faites-en la somme et la
moyenne, que vous achez à chaque demande.
1. Créez une page HTML en utilisant le bouton +onload, à droite de
la fenêtre de l'éditeur LemanOS, à côté de html. Ajoutez-y la balise
<script> au moyen du bouton correspondant et une fonction dans cette
balise, au moyen du bouton situé dans le tableau situé en dessous du
premier. Cette fonction sera appelée au chargement de la page, après
que tous les éléments auront été dessinés. Vous devriez obtenir une
page comme ci-dessous. Chargez cette page, les alertes vous montreront
quelles instructions sont exécutées à quel moment :
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;
charset=utf-8"/>
<title>nomDeFichier</title>
<script type="text/javascript">
alert("Affiché AVANT que la page soit dessinée")
function execute(arg) {
alert("Affiché APRES que la page soit dessinée")
3.6.
67
EXERCICES
// placement de votre code
}
</script>
</head>
<body onload="execute()">
</body>
</html>
2. Dénissez une variable globale, c'est-à-dire dénie avant la fonction
pour contenir la somme des valeurs entrées.
3. Introduisez une boucle for qui appelle la fonction prompt(). Ajoutez la
valeur entrée par l'utilisateur à la somme globale. Après chaque entrée,
acher la valeur de cette variable, divisée par la variable de boucle,
dans une fenêtre alert(), en ajoutant le nom Moyenne devant la valeur.
4. Utilisez un champ input à la place de prompt() et l'instruction innerHTML pour acher le résultat dans une balise <div> ou <span>.
5. Mémorisez et achez dans un champ prévu pour cela, toutes les valeurs
entrées. Pour ajouter les nombres au tableau en vue de les mémoriser,
vous pouvez utiliser soit liste[i] soit l'instruction liste.push.
3.6.2 ex - Diverses calculettes
La première calculette ne fera que des additions :
1. Créez deux champs d'input au moyen du bouton correspondant sur
la page LemanOS. Quand on tape un retour de ligne dans le champ
d'input, achez la somme des deux nombres sur la page, au moyen de
innerHTML.
2. Placez une instruction select entre les deux champs. Cette instruction
orira les options +, -, *, /, qui permettront à l'utilisateur de faire
l'une des 4 opérations. Utilisez un switch pour diriger le programme
sur l'opération qui découle de l'option.
Chaque fois qu'on clique un bouton, on utilise le dernier résultat comme
premier argument de l'opération. Vous pouvez également ajouter des boutons
1, 2, 3..., un bouton sinus, ou d'autres fonctions mathématiques.
1. Ne mettez qu'un champ input. Dénissez des boutons HTML +, -, *,
/ et Mettre à zéro plutôt que la sélection. Tous les boutons appellent
la même fonction, mais avec un argument diérent (1,2,3..).
68
CHAPITRE 3.
INTERACTIONS HTML - JAVASCRIPT
En fait, un autre moyen de faire une calculette est de lire une expression
telle que (2+3)*44 dans le champ d'input et en mettant cette expression dans
un appel eval(txt)(3.1.3).
4. Essayez cette solution.
3.6.3 ex - Mouvement d'une image dans un rectangle
Introduire une image dans une page et la faire bouger régulièrement entre
deux limites horizontales et deux limites verticales. Utilisez pour cela la fonction setInterval dans une fonction appelée depuis l'attribut onload de la balise
<body>.
La structure du programme peut être proche de celle qui a été présentée
à la section 3.3 et qui sépare les calculs de l'achage.
1. Dénissez une image dans la page 2
2. Dénissez des variables qui contiennent les incréments qu'on ajoute à
des variables x et y, qui sont ensuite utilisée pour placer l'image.
3. Quand la valeur de x devient trop petite ou trop grande, on inverse la
valeur de l'incrément.
Prévoyez un bouton qui accélère et un autre qui décélère la vitesse de
l'image. Il sut de changer la valeur des incréments (proportionnellement si
les incréments ne sont pas identiques sur x et y.
Prévoyez un bouton qui arrête le mouvement.
4. Pour cela, introduisez une variable true/false. La fonction appelée par
le temporisateur teste cette dernière au début et eectue un retour sans
déplacer l'image si elle est false.
5. A chaque clic sur le bouton d'arrêt, on inverse la valeur de cette variable : enRoute = ! enRoute.
Notez que pour bien maîtriser son programme, il faut attribuer à une
variable comme enRoute ci-dessus, la valeur true quand l'action que son nom
symbolise est activée. On a souvent tendance à utiliser true pour l'opération
qu'on est en train d'implémenter, ici l'arrêt. Dans ce cas il faut absolument
la nommer arret, mais il est encore mieux de décrire le côté positif de l'action
dans le nom d'une telle variable, comme proposé ici : enRoute si true. Le
pire est de nommer une variable pasInitialise , par exemple, car on ne sait
plus quand elle est true et l'on doit faire des réexions pénibles lorsqu'elle
est testée en compagnie d'autres variables.
2. Vous pouvez en récupérer une sur votre bureau et la charger au moyen du menu
Fichier > Charger
3.6.
EXERCICES
69
3.6.4 ex - Suivi du curseur
Pour faire des jeux, il est souvent intéressant de décider de mouvements en
fonction de la position de la souris. Par exemple on peut dessiner une raquette
symbolique, la déplacer et faire rebondir une balle si elle arrive en contact
avec la raquette. Pour cela on utilise l'événement onmousemove. Quand il
est déni, cet événement survient chaque fois que la souris est déplacée d'une
petite distance. On peut alors récolter sa position au moyen de l'objet event
(paragraphe 3.2.4).
1. Placer l'événement onmousemove dans la balise <body> et lui faire
appeler une fonction : onmousemove="bouge(event)".
2. Dans cette fonction, mettre un argument pour récupérer event et positionner une image à l'endroit du curseur. Sous Firefox, la position du
curseur est donnée par event.clientX ; event.clientY.
Essayez le programme, vous verrez que l'image suivra la souris, comme si
elle était collée au curseur.
En combinant cet exercice avec le précédent, vous pouvez faire rebondir
l'image sur la raquette...
3.6.5 ex - Morpions
Partant à nouveau de l'exemple de la section 3.3, créez le jeu des morpions. Le programme initial est très simple mais peut devenir de plus en plus
sophistiqué, en réalisant les opérations dénies plus loin.
Les opérations sont activées par les événements suivants :
Un bouton d'initialisation remet tout à zéro.
A chaque clic de l'utilisateur dans le tableau, le programme place le pion
à l'endroit cliqué, puis place son propre pion selon les critères décrits
ci-dessous.
Un bouton permet à l'utilisateur de demander au programme de placer
un pion. Ce bouton sera utilisé quand l'utilisateur veut laisser le programme commencer le jeu. Après un clic sur ce bouton, le programme
se continue comme ci-dessus.
Le programme gère un tableau qui contiendra 0 pour une case libre, 1 si
la case est occupée par un pion de l'ordinateur et 2 si elle est occupée par un
pion de l'utilisateur. Notez que ce n'est pas une bonne idée que d'utiliser le
tableau HTML lui-même (par exemple la couleur des cases) pour savoir ce
qui a été joué, car on mélangerait alors la logique et l'achage.
70
CHAPITRE 3.
INTERACTIONS HTML - JAVASCRIPT
La fonction d'achage transfère les données du tableau des pions dans les
couleurs du tableau HTML d'achage. Ensuite, si son argument est déni
et/ou pas nul, il est aché dans une fenêtre d'alerte.
On peut mettre chaque opération dans une fonction. Ces fonctions devraient retourner true si elles ont pu eectuer l'opération demandée et false
sinon.
1. Mettre le pion de l'utilisateur écrire une fonction qui met la valeur
passée en argument (1 == pion utilisateur, 2 == pion ordinateur) dans
le tableau des pions
2. Vérier s'il y a un triplet, auquel cas on annonce que l'utilisateur a
gagné
3. Vérier si le tableau est plein, auquel cas on annonce qu'il y a match
nul
4. Mettre le pion à une position tirée au hasard. Si la position est occupée
(l fonction de placement retourne false, retirer une nouvelle position.
Après avoir placé le pion, revérier s'il y a un triplet, auquel cas l'ordinateur a gagné.
5. Avant de tirer une position au hasard, vérier si l'ordinateur a déjà aligné deux pions et placer le pion dans la case libre créer une fonction
avec, en argument, les pions (utilisateur ou ordinateur) recherchés et
qui retourne soit le pion qui reste libre sur une ligne, soit -1 (compliqué,
trouverez-vous plus simple que notre solution, deuxPions ?)
6. Avant de tirer une position au hasard, vérier si l'utilisateur a déjà
aligné deux pions et placer le pion dans la case libre, si c'est le cas.
7. Essayer de placer un deuxième pion sur une ligne où il y a déjà un pion
de l'ordinateur et encore une case vide.
8. Vérier si l'utilisateur a la possibilité de créer deux lignes avec deux
pions à son prochain coup et l'en empêcher dans ce cas.
9. Au premier coup, placer le pion au hasard dans un coin ou au milieu.
10. Essayer de préparer deux lignes avec deux pions.
3.6.6 ex - Page structurée
Créez une page dont la structure est donnée par la gure 3.1, une structure
qu'on retrouve souvent.
L'en-tête contient un chier identique pour toutes les pages (une iframe).
Pour qu'il couvre toute la largeur du tableau, on utilise colspan.
x=
3
5
y = x.shift() Æ
x=
3.6.
71
EXERCICES
Iframe éditable
Figure 3.1 Page structurée
La page principale est éditable et les autres champs sont xes. Le tout
est construit dans une table.
Pour que la colonne de droite couvre les deux dernières colonnes, on utilise
rowspan. Si l'on utilise rowspan, il ne faut pas mettre de <td> dans les
colonnes correspondantes sur les lignes autres que celle qui contient rowspan.
5
9
5
x.push(7)
x=
Pied de page
9
y = x.pop() Æ
x=
aaa
2
x.unshift(9)
x=
Iframe avec fichier
5
9
5
72
CHAPITRE 3.
INTERACTIONS HTML - JAVASCRIPT
12 août 2010
Chapitre 4
Fichiers
4.1 Introduction
Le système LemanOS permet de créer des programmes qui lisent des
chiers, en créent de nouveaux ou en eacent. Ces chiers sont accessibles
au moyen de l'éditeur.
Les primitives de gestion de ces chiers ne sont valables qu'avec l'environnement LemanOS. Ils sont liés aux comptes personnels. Toutefois, on retrouve
les fonctions présentées dans ce chapitre dans la plupart des langages, sous
une forme plus ou moins semblable.
Les appels sont disponibles sous une forme simple et sous une forme qui
permet des opérations un tout petit plus complexe. La deuxième version, qui
devrait encore complétée dans le futur utilise le concept d'objet.
Pour que les commandes présentées dans ce chapitre puissent être exécutées (pour les deux approches), il faut rajouter les lignes suivantes dans le
bloc <head> de votre chier.
<script src="/LemanOS/dwr/engine.js">
< /script>
<script src="/LemanOS/files.js">
< /script>
4.1.1 Visualisation des chiers dans l'éditeur LemanOS
Les chiers que vous créez n'apparaissent dans la liste des répertoires que
lorsque vous rechargez la page de l'éditeur (ou qu'elle se recharge suite à
quelque manipulation).
Si le chier est ouvert dans l'éditeur de LemanOS avant qu'on y écrive des
données, il faut utiliser Fichier > Rafraîchir ou presser le bouton gauche de
la souris sur le nom du chier dans la liste des chiers, pendant une fraction
73
74
CHAPITRE 4.
FICHIERS
de seconde et sélectionner le même menu, Rafraîchir, pour voir le nouveau
contenu.
4.2 Librairie simple
Les commandes suivantes permettent de gérer les chiers :
File.write(name, s) // écrit le contenu de s dans le chier de nom name.
Si le chier existait avant l'opération, son ancien contenu est remplacé
par les nouvelles données.
File.append(name, s) // ajoute le contenu de s à la n du chier de nom
name
var x = File.read(name) // introduit le contenu du chier de nom name
dans la variable x
File.remove(name) // eace le chier de nom name
Si l'opération n'a pas pu être eectuée, un message indiquant la source
de l'erreur est disponible dans File.error. Ainsi, il faudrait après chaque opération tester ce message :
if (File.error!=null) alert(File.error)
et donner au programme les instructions lui indiquant de qu'il doit faire dans
ce cas.
Dans le cas où il y a une erreur de lecture, l'opération retourne null dans
la variable aectée.
4.3 Librairie similaire à celle de Java
4.3.1 Lecture d'un chier
Le code ci-dessous utilise le concept d'objet détaillé au chapitre 8. Pour
manipuler un chier, on crée un objet de constructeur File en lui passant le
nom du chier sur lequel on désire agir.
var file = new File("aaaa.html")
var texte = file.read()
if (file.error!=null) // il y a une erreur
alert(file.error)
}
file.close()
4.4.
EXERCICES
75
4.3.2 Création / écriture dans un chier
Le code ci-dessous ouvre un objet File en lui passant le nom du chier
sur lequel on désire agir, puis il écrit un texte dans ce chier.
var file = new File("aaaa.html")
file.write("un contenu")
if (file.error!=null) // il y a une erreur
alert(file.error)
}
file.close()
4.3.3 Adjonction à la n d'un chier
L'instruction suivante est semblable à l'instruction write, mais plutôt que
de remplacer le contenu du chier, s'il existe, par le texte en argument, elle
ajoute les données à la suite de ce contenu.
var file = new File("aaaa.html")
file.append("une adjonction")
if (file.error!=null) // il y a une erreur
alert(file.error)
}
file.close()
4.3.4 Elimination d'un chier
Les instructions ci-dessous permettent d'eacer un chier. Le mot delete
est réservé en Javascript, raison pour laquelle on n'utilise pas ce dernier.
var file = new File("aaaa.html")
file.remove()
if (file.error!=null) // il y a une erreur
alert(file.error)
}
file.close()
4.4 Exercices
4.4.1 ex - Fichier HTML
Cet exercice vous propose de créer une page HTML par programme et
d'y insérer l'heure à laquelle elle a été crée.
76
CHAPITRE 4.
FICHIERS
1. Créez une page HTML et entrez toutes ses lignes dans une chaîne de
caractères en encadrant chaque ligne qui la compose entre apostrophes
(ou guillemets si la ligne contient des apostrophes).
2. Ajoutez \n à la n de chaque ligne, à l'intérieur des apostrophes et
insérez un + entre les lignes pour les concaténer en une seule chaîne de
caractères.
3. Reréez, dans le même chier, une page dans laquelle la chaîne que vous
venez de dénir est aectée à une variable et écrite dans un chier.
Chargez la page pour exécuter le programme qui crée le chier.
4. Rechargez l'éditeur LemanOS pour faire apparaître le nouveau chier
dans la liste des répertoires. Achez son lien et cliquez-le pour ouvrir
la page que vous avez créée par programme.
5. Introduisez +Date()+ entre deux lignes, pour concaténer la date parmi
les lignes de la page et la faire apparaître sur la page. Complétez les
lignes de part et d'autre de cette date pour qu'elle soit intégrée dans
un élément HTML qui la mette en évidence.
4.4.2 ex - Sauvetage de variables
Pour sauver les valeurs de variables, on peut les écrire dans un chier en
les séparant par des signes qui n'apparaîtront pas dans les textes sauvés, par
exemple <@@>.
1. Dénissez les variables var x=23, y=45, z="bonjour"
2. Poussez (push) les contenus de ces variables dans un tableau, puis sauvez le contenu au moyen de join(signe), ce qui formera une seule chaîne
dans laquelle les valeurs sont séparées par les signes choisis.
3. Relisez cette chaîne et séparez-la (split) en ses éléments que vous acherez sur la page.
4. On peut alors aecter chaque élément à la variable correspondante pour
restaurer les variables.
Notez que le chapitre suivant présente une façon plus complète de sauver
des variables.
12 août 2010
Chapitre 5
Arbres DOM et XML
5.1 Introduction
Lorsque le navigateur doit acher une page, il doit d'abord l'analyser et
déposer toutes les caractéristiques des éléments dans une structure qui lui
permette de prévoir les diérentes grandeurs des éléments. En particulier, il
faut qu'il connaisse l'élément le plus large d'une table avant de commencer à
la dessiner.
La structure qui va lui permettre de faire cette analyse est un arbre,
structure qu'on retrouve dans de nombreuses applications de l'informatique.
Dans cette section, nous allons étudier les arbres qui permettent de traiter
les pages XHTML et les chiers XML. Les pages HTML qui ne suivent pas
la règle impliquée par le X de ces symboles doivent être complétées de façon
un peu arbitraire pour les représenter sous la forme d'un arbre, aussi nous
ne verrons que des exemples standardisés.
L'arbre qui est préparé pour l'achage d'une page HTML pour le navigateur reste accessible par les instructions Javascript après son utilisation.
On peut d'ailleurs modier cet arbre pour introduire de nouveaux éléments,
ce qui est parfois plus exible que l'utilisation de innerHTML.
Cette représentation arborescente est appelée DOM, pour document
object model .
XML est un langage similaire à HTML (ou mieux XHTML). Pour le
traiter, on peut utiliser les fonctions regroupées sous le nom d'AJAX. Ce nom
recouvre une technique d'utilisation des transmissions entre une page et le
serveur qui a fourni la page, qui utilise des moyens parfaitement standards.
Cette technique permet de recharger des portions de pages sans recharger
toute la page. C'est la technique qui est utilisée pour faire tourner l'éditeur
LemanOS.
77
78
CHAPITRE 5.
ARBRES DOM ET XML
Comme ce manuel n'explique pas comment créer des programmes du côté
serveur, nous ne présenterons qu'un aspect particulier d'AJAX, la récolte de
données mémorisées sous forme de chiers et l'analyse de contenus de chiers
XML. Les fonctions d'AJAX permettent de gérer XML avec les mêmes primitives que celles qui gèrent le DOM, ce qui explique que les deux sujets
soient groupés dans le même chapitre.
Si l'on crée des programmes du côté du serveur, ils devront fonctionner
comme s'ils envoyent des chiers. Bien sûr, de nombreuses librairies de codage des informations sont disponibles, mais l'aspect que nous étudierons
est complet en ce qui concerne le côté du navigateur et cette approche nous
permettra d'étudier les arbres et XML.
5.2 Représentations graphique
Pour étudier les opérations qu'on peut eectuer sur un arbre, il est indispensable de le représenter sous une forme graphique. Cette représentation
nous permettra d'expliquer comment le parcourir ou le modier.
La page simpliste suivante peut être représentée des deux façons apparaissant sur les gures 5.1 et 5.2.
<body>
<table>
<tr>
<td>text1</td>
<td>text2</td>
</tr>
<tr>
<td>text3</td>
<td>text4</td>
</tr>
</table>
</body>
La représentation de la gure 5.1 suit de très près la structure d'une
page, mais elle devient vite dicile à créer et il est également impossible de
se représenter un parcours systématique de tous les n÷uds qu'elle contient.
Cette gure n'est d'ailleurs pas complète. On doit donc utiliser la vision de
la gure 5.2.
La balise <html>, qui se referme à la n de la page, englobe tous les autres
éléments. C'est ce qu'on peut lire dans la deuxième gure, par convention.
La balise <table> est contenue dans la balise <body>, elle-même contenue
dans la balise <html>. Et ainsi de suite.
5.2.
79
REPRÉSENTATIONS GRAPHIQUE
html
head
body
table
Figure 5.1 Représentation réaliste
html
head
body
table
tr
td
texte1
tr
td
texte2
td
texte3
td
texte4
Figure 5.2 Représentation sous forme d'arbre
80
CHAPITRE 5.
ARBRES DOM ET XML
La balise <html> est appelée la racine de l'arbre, les autres n÷uds forment
les branches et les n÷uds d'extrémités sont naturellement appelés feuilles.
Notez que pour les informaticiens, les arbres poussent vers le bas !
A chaque étage, on utilise les analogies familiales pour décrire les relations
entre les n÷uds. Ainsi <table> est l'enfant de <body>, lui-même enfant de
<html>. Dans l'autre sens, <body> est le parent de <table>.
Ainsi l'enfant du deuxième enfant du premier enfant de <table> est text2.
5.3 Manipulation d'un arbre au moyen de Javascript
Dans cette section sont présentées quelques façons de gérer l'arbre DOM
d'une page achée par un navigateur.
5.3.1 N÷uds et leurs attributs
Les caractéristiques (sorte de n÷ud, enfant, parent) des n÷uds de l'arbre
sont déposés dans des attributs. Un attribut est accédé par une instruction
Javascript au moyen d'un point, comme ce que nous avons vu au paragraphe
3.2.1. Si node, qu'on a obtenu soit au moyen de getElementById, soit par le
moyen décrit juste ci-dessous, contient un n÷ud, on peut lire son attribut
comme suit :
var attr = node.textContent
Un n÷ud vu par Javascript contient les attributs suivants :
node = document.body
node.nodeName
node.tagName
node.childNodes
node.childNodes[2]
node.childNodes.length
node.parentNode
node.textContent
document est déni dans toute page HTML
nom du n÷ud: BODY, TABLE, TR, TD, #text
autre possibilité d'obtenir le nom du n÷ud
tableau d'enfants pointant sur les n÷uds enfants
enfant numéro 2
nombre d'enfants du n÷ud
n÷ud parent
regroupement de tous les textes placés sous ce
n÷ud
document est automatiquement déni dans chaque page. Cette entité
contient l'attribut body qui pointe toujours sur le n÷ud <body> de la page.
Les éléments html et head ne sont pas accessibles directement, il faut les
accéder depuis body par le biais des attributs parentNode et childNodes. Le
5.3.
MANIPULATION D'UN ARBRE AU MOYEN DE JAVASCRIPT
81
contenu de head n'est d'ailleurs pas visible sur la page et n'a pas besoin d'être
traité dans un arbre.
node reçoit la référence du n÷ud body dans la première ligne ci-dessus,
mais il pourrait représenter n'importe quel autre n÷ud sur les autres lignes.
Les attributs nodeName et tagName contiennent la même information.
Cependant le deuxième n'est pas déni dans les n÷uds de texte. Pour parcourir un arbre de façon uniforme, il est plus simple d'utiliser nodeName.
De tels inconsistences arrivent parfois dans les librairies. Une fois que certaines options ont été prises, on ne peut plus les enlever, car il faut garder la
compatibilité des navigateurs avec les millions d'applications déjà créées.
L'attribut childNodes est constitué d'un tableau. Chaque élément de ce
tableau contient un n÷ud enfant. Comme tout tableau vu à la section 2.17,
on adresse un de ses éléments au moyen des parenthèses carrées. Le nombre
d'enfants est donc bien évidemment égal à la longueur du tableau (.length).
L'attribut parentNode contient la référence du n÷ud parent. Attention,
cet attribut n'est mis à jour que lorsque l'arbre est construit par le navigateur,
pas lorsque les n÷uds sont ajoutés par programme.
L'attribut textContent contient le regroupement de tous les textes placés
dans les éléments du sous-arbre dont le n÷ud considéré forme la racine.
Ainsi, théoriquement (à part un détail pratique vu dans l'exercice 5.5.2),
l'enfant du deuxième enfant du premier enfant de <table> est text2. Il est
obtenu au moyen d'instructions Javascript de la façon suivante :
document.body.childNodes[0].childNodes[0].childNodes[1]
.childNodes[0].textContent
document.body.childNodes[0] est le n÷ud correspondant à la balise
<table>, c'est-à-dire le premier attribut de <body>. L'attribut childNodes
est référencé en ajoutant .childNodes, dont on trouve l'élément 0 en ajoutant
[0], etc. textContent contient le texte de l'élément pointé ou du sous-arbre.
innerHTML contient le même texte, mais avec les balises.
5.3.2 Introduction de n÷uds dans l'arbre
La deuxième instruction ci-dessous crée un n÷ud non-texte et la troisième
l'introduit dans le n÷ud (supposé être de type TR) de l'arbre DOM.
unNoeud = document.getElementById("unTR")
nouvTD = document.createElement("TD")
unNoeud.appendChild(nouvTD)
Pour introduire un n÷ud de texte dans l'élément nouvTD créé ci-dessus,
il sut de procéder de la façon suivante :
txt = document.createTextNode("mon texte")
nouvTD.appendChild(txt)
82
CHAPITRE 5.
ARBRES DOM ET XML
Pour ajouter un sous-arbre dans un tableau childNodes, il faut remplacer
un élément par le sous-arbre et intégrer l'élément remplacé dans le sous-arbre.
5.3.3 Elimination de n÷uds
Pour éliminer un n÷ud de l'arbre, il faut connaître sa référence. On a
également besoin du parent, mais le parent est automatiquement dérivé de
l'enfant. L'instruction ci-dessous élimine le n÷ud unNoeud de unParent dans
un arbre.
unParent.removeChild(unNoeud)
Il en découle que le deuxième enfant d'un n÷ud est éliminé par l'instruction ci-dessous (le premier n÷ud a le numéro 0) :
unParent.removeChild(unParent.childNodes[1])
Si l'on a obtenu un n÷ud, quand il a été cliqué ou qu'on le repère dans
une table dont on sait quel n÷ud on désire éliminer, on peut le retirer de
l'arbre au moyen de :
unNoeud.parentNode.removeChild(unNoeud)
5.3.4 Parcours automatique d'un arbre
Pour parcourir tous les enfants d'un n÷ud, leNoeud, on utilise la fonction
appelée sur la deuxième ligne ci-dessous. Cette fonction n'introduit encore
rien de nouveau.
function parcourtEnfants(arg) { // définition
for (var i=0; i<arg.childNodes.length; i++) {
var nd = arg.childNodes[i]
alert(nd.nodeName)
}
}
leNoeud = document.getElementById("leNoeud")
parcourtEnfants(leNoeud) // appel
Cette fonction va faire apparaître les noms de tous les enfants, l'un après
l'autre, dans une fenêtre d'alerte.
La fonction parcourtPetitsEnfants ci-dessous ache les petits enfants de
chaque enfant après que l'enfant a été aché. Elle est semblable à la fonction précédente, parcourtEnfants, mais en plus, elle appelle cette fonction
précédente en passant le n÷ud enfant après l'achage de chaque enfant.
function parcourtPetitsEnfants(arg) {
for (var i=0; i<arg.childNodes.length; i++) {
var nd = arg.childNodes[i]
5.3.
MANIPULATION D'UN ARBRE AU MOYEN DE JAVASCRIPT
83
alert(nd.nodeName)
parcourtEnfants(nd)
}
}
Si vous avez compris ce qui se passe dans cette fonction, vous avez certainement deviné la suite. En eet, si l'on remplace l'appel à parcourtEnfants
par un appel à la méthode-même, c'est-à-dire de nouveau parcourtPetitsEnfants, les appels successifs (on dit récursifs) passeront automatiquement par
tous les n÷uds de l'arbre, de la racine aux feuilles, quelle que soit la grandeur
de l'arbre.
Ce parcours de l'arbre est fait un peu à la manière d'un peintre qui peint
un tableau représentant sa chambre, dans laquelle est accroché le tableau
qu'il est entrain de peindre. Dans le tableau, il y a une copie du tableau,
dans lequel il y en a une autre, etc, jusqu'à ce que le tableau soit si petit
qu'il est représenté par un point, qui n'est donc plus le tableau. Notez que
sans cette précaution, il y aurait une innité de tableaux les uns dans les
autres, ou une innité d'appel dans notre cas.
5.3.5 Stucture faite au moyen de tableaux associatifs
Voici une solution qui utilise les tableaux et tableaux associatifs vus au
paragraphe 2.17 pour représenter des arbres. Le tableau ci-dessous représente
un arbre qui n'a que deux n÷uds de même niveau.
{prenom : "Peter", nom: "Tagasi"}
On peut inclure ce tableau dans un autre :
{ identité : { prenom : "Peter", nom: "Tagasi"} }
Pour inclure plusieurs noms au même niveau, comme les childNedes du
paragraphe précédent, on utilise les tableaux simples :
{ liste : [
]
}
{ identité: { prenom : "Peter", nom: "Tagasi"} },
{ identité: { prenom: "Hans", nom: "Vogel" } }
ce qui produit alors l'arbre ci-dessous.
liste
identité
identité
84
CHAPITRE 5.
ARBRES DOM ET XML
Pour atteindre le champ qui contient Tagasi, par exemple, on suit les
conventions qui dénissent l'élément d'un tableau ou d'un tableau associatif :
tableau.liste[0].identite.nom
Le symbole identite est redondant, car le même champ pourrait être atteint si l'on enlève ce symbole dans la dénition et dans l'appel. Toutefois il
nous permet de donner un nom à la liste de n÷uds.
Les instructions qui parcourent l'arbre sont semblables à celles du paragraphe précédente et pourraient même être identiques si l'on choisissait
soigneusement les noms des attributs des n÷uds.
Le type de dénition ci-dessus est utilisé pour passer des structures d'un
serveur à une page HTML. Il est à la base des librairies JSON (voir ce nom
sur Internet).
L'exercice 5.5.4 illustre ce concept.
5.4 AJAX et chiers XML
Notez que pour garantir la sécurité des applications, l'approche décrite
dans cette section ne permet d'accéder qu'au serveur qui a envoyé la page.
En eet, sans cette limitation il serait possible d'envoyer des données condentielles à des serveurs tiers.
5.4.1 XML
Le langage XML est structuré en balises dotées d'attributs comme
XHTML. Cependant les données qu'il décrit sont propres à chaque utilisation
et la signication des balises est déterminée par le développeur.
Le tableau
{prenom : "Peter", nom: "Tagasi"}
est représenté en XML par :
<prenom>Peter< /prenom><nom>Tagasi< /nom>
chacun des champs étant encadré par les balises ouvrantes et fermantes.
Ainsi, les lignes suivantes décrivent une liste de personnes qui auraient
emprunté quelques livres. On pourrait représenter une partie de l'information
dans les attributs des balises, mais cette information ne devrait théoriquement décrire que des aspects diérents de ceux qui sont codés dans les balises
par exemple des diérences de codage. Le placement des informations est toutefois arbitraire et diérents développeurs arriveront à diérents codages.
<emprunts>
<personne>
<identite>
5.4.
AJAX ET FICHIERS XML
85
<prenom>Peter</prenom>
<nom>Tagasi</nom>
</identite>
<livres>
<titre>Tarzan dans les arbres</titre>
<titre>Jane sur une branche</titre>
</livres>
</personne>
</emprunts>
Le fait que les balises sont placées sur une ou plusieurs lignes n'a pas de
signication. Si plusieurs personnes ont emprunté des livres, on répétera le
bloc <personne> à l'intérieur du bloc <emprunts>
Toutes les balises sont appairées. Le bloc total du chier est englobé dans
une paire de balises. Les textes formant l'information sont placés dans les
balises, jamais entre des blocs. On trouve toutefois des espaces et des retours
de lignes entre les blocs.
5.4.2 Lecture d'un chier XML
Le code ci-dessous peut être entré dans une page et exécuté tel quel par
Firefox. Cependant il faut encore traiter les erreurs et prévoir des extensions
pour certains autres navigateurs. Le code complet est disponible à l'adresse
http://siteLivre/AJAX, mais les éléments essentiels à sa compréhension sont
inclus dans le listing ci-dessous.
1
2
3
4
5
6
7
8
function makeRequest(URL) { // définition
http_request = new XMLHttpRequest()
http_request.open('GET', URL, false)
http_request.send(null)
return http_request.responseXML
}
var xml = makeRequest("Tree.xml") // appel
var aaaaTbl = xml.getElementsByTagName("aaaa")
La fonction makeRequest lit un chier dont le nom est placé dans l'appel.
Il s'agit ici du nom relatif à la page dans laquelle l'appel se trouve (contrairement aux fonctions d'accès de chiers de LemanOS). Un nom seul est donc
recherché dans le même répertoire que la page.
86
CHAPITRE 5.
ARBRES DOM ET XML
La ligne 2 crée un objet interne qui sera utilisé pour les opérations suivantes. La ligne 3 initialise le transfert et la ligne 4 l'actionne. Lorsque la
fonction send est terminée, le résultat est disponible 1 dans l'attribut responseXML. Le résultat correspond à la racine de l'arbre du code XML du chier
lu. Cette racine est retournée à l'appelant.
Si l'on remplace responseXML à la ligne 5 par responseText, on obtient
le texte non travaillé, tel qu'il apparaît dans le chier.
5.4.3 Extraction des éléments du DOM
Les éléments peuvent être extraits soit en suivant l'arbre XML comme
on suivait l'arbre DOM, soit en utilisant les fonctions dénies ci-dessous.
Comme il n'y a pas d'identicateur dans l'arbre, il faut utiliser la fonction getElementsByTagName, plutôt que la fonction getElementById. getElementsByTagName récupère un élément par le biais du nom de sa balise 2 . Cette
fonction retourne un tableau (d'où le s après Element), car il peut y avoir
plusieurs n÷uds avec la même balise dans un sous-arbre. Il faut donc utiliser
les parenthèses carrées qui déterminent le rang de l'élément recherché, même
s'il n'y en a qu'un.
La partie placée entre les deux bornes d'une balise est récupérée au moyen
de rstChild.nodeValue. L'attribut rstChild est ici la feuille de la dernière
balise, le texte entre les bornes de cette balise étant en eet placé dans
un enfant supplémentaire de la balise. Si la variable xml ci-dessous contient
l'arbre déni dans le paragraphe 5.4.1, alors
xml.getElementsByTagName("emprunts")[0].childNodes[0]
.childNodes[0].childNodes[0].firstChild.nodeValue
retourne : Peter
rstChild est l'équivalent de childNodes[0], on peut utiliser l'un ou l'autre.
Attention, on oublie souvent d'ajouter le premier index, [0], ou la dernière
étape qui arrive jusqu'à la feuille.
getElementsByTagName permet également de récupérer les listes d'éléments internes, sans passer par la première, ni par tous les n÷uds intermédiaires.
xml.getElementsByTagName("personne")[0]
.getElementsByTagName("titre")[1].firstChild.nodeValue
retourne : Jane sur une branche
1. Dans le cas où le troisième paramètre de la ligne 3 est false
2. getElementsByTagName est également valide dans les arbres HTML
5.4.
87
AJAX ET FICHIERS XML
5.4.4 Lecture asynchrone d'un chier XML
L'appel du paragraphe précédent est synchrone (ou bloquant), c'est-à-dire
que la fonction ne se termine que quand l'opération est terminée. Cependant,
le premier A de AJAX signie asynchrone , c'est-à-dire qu'on lance l'envoi
de la requête et que le navigateur peut traiter un autre événement, s'il y en a
un en attente, avant de traiter le retour du serveur. C'est ce qui est présenté
dans ce paragraphe. Cela est particulièrement intéressant si le serveur ne
répond pas immédiatement.
L'appel ci-dessous est asynchrone, car le troisième paramètre de la ligne6
est true.
1
2
3
4
5
6
7
8
9
function makeRequest(URL, alertFunction) {
http_request = new XMLHttpRequest()
http_request.onreadystatechange = function() {
alertFunction(http_request,URL)
}
http_request.open('GET', URL, true)
http_request.send(null)
return
}
11
var alertContents = function (http_local_request, URL) {
document.getElementById("Display").innerHTML
12
}
13
makeRequest("fiches.xml", alertContents)
10
= http_local_request.responseXML
Dans le fonctionnement asynchrone, la fonction send de la ligne 7 revient
avant que le transfert soit eectué.
Quand les données sont revenues du serveur, une fonction est appelée,
exactement comme dans le cas des événements ou des timeout. Pour indiquer
quelle méthode le navigateur doit appeler lorsque la réponse arrive, on la
passe comme deuxième paramètre de l'appel disposé sur la ligne 13. Cette
méthode est décrite à la ligne 10. Elle reçoit comme paramètre l'objet préparé
par l'appel et le nom du chier recherché. Le premier paramètre de cette
seconde fonction est utilisé pour passer la réponse et le deuxième paramètre
de cette fonction, par exemple, pour acher le nom du chier en cas d'erreur
(chier URL non trouvé).
88
CHAPITRE 5.
ARBRES DOM ET XML
5.4.5 Ecriture d'un chier XML
La constitution d'un chier XML au moyen de Javascript est très simple,
mais ne possède pas d'approche standard comme la lecture. La création de chiers XML (au moyen de File.write) permet de sauver des structures comme
celle du paragraphe 5.3.5, en vue de leurs restaurations ultérieures. Le code
ci-dessous permet de sauver temporairement des balises dans un tableau et
d'envoyer le tout dans un chier.
var temp = []
temp.push("<html>")
temp.push("<h1>" + txt + "<h1>")
temp.push("</html>")
File.write("tmp.xml", temp.join("\n"))
Le paramètre de join assure que si on lit le chier pour le vérier, il ne
sera pas sur une seule ligne. L'appel File.write est décrit au chapitre 4.
5.5 Exercices
5.5.1 ex - Examen du DOM au moyen du dévermineur
Créez une nouvelle page HTML qui contient la table du paragraphe 5.2,
puis cliquez avec le bouton droit de la souris sur cette page et sélectionnez le
menu Inpecter un élément. Vous verrez apparaître l'arbre du DOM en dessous
de la page HTML. En promenant la souris sur les éléments, vous verrez où
ils sont logés dans l'arbre.
On obtient le même eet en cliquant le symbole correspondant sur la
gauche de la barre de menu du dévermineur. Si vous cliquez le menu HTML
en haut de la fenêtre du dévermineur, vous verrez l'arbre de la page, mais
sans sélection. Par contre si vous cliquez sur les éléments de l'arbre, vous
verrez où ils se trouvent dans la page.
Si vous cliquez maintenant sur le menu Script, une nouvelle fenêtre s'ouvre
sur la droite de la fenêtre. Tapez document dans le champ qui contient Nouvelle expression... et cherchez l'attribut body. Cliquez sur la petite croix qui
se trouve à gauche de cet attribut, cherchez maintenant l'attribut childNodes
et cliquez sur la croix qui se trouve à sa gauche, vous verrez la liste des enfants
du n÷ud body. Vous pouvez continuer ainsi pour voir tous les éléments.
Ces fenêtres permettent de trouver des erreurs dans la construction d'une
page. Les instructions de Javascript nous permettent de suivre les enfants
présentés dans cette dernière fenêtre.
5.5.
EXERCICES
89
5.5.2 ex - Elément accédé au moyen de Javascript
Dans cet exercice, on vous propose de créer un programme qui accède aux
éléments mentionnés au paragraphe précédents.
Notez cependant que l'exemple du paragraphe 5.2 a été légèrement simplié pour ne pas embrouiller les explications. En fait, les tables HTML
intègrent dans tous les navigateurs un étage de plus, qu'on utilise rarement,
entre les balises <table> et <tr> :
<table><tbody><tr></tr></tbody></table>
D'autre part, les espaces et les retours de lignes entre la n d'une balise
et le début de la suivante apparaissent dans l'arbre : <td></td> espace
<td></td>.
Pour faire l'exercice, il faut compter cet élément intermédiaire et ne mettre
aucun retour de ligne ni espace entre <body> et </body>. Il faut donc écrire
tout ce qui se trouve entre ces balises sur la même ligne.
1. Créez une page HTML qui contient la table dénie au début de la
section 5.2.
2. Créer la fonction qui retrouve l'élément table au moyen de getElementById puis retourne l'élément text2 au moyen d'un commande semblable
à celle qui est décrite à la n du paragraphe 5.3.1, mais qui part de la
table, au moyen de getElementById et qui prend en compte tbody.
5.5.3 ex - Parcours d'arbre
On vous propose de créez un programme qui ache dans une balise <div
id="achage">, au moyen de innerHTML, une description de l'arbre de la
table ci-dessus. Attention, n'achez cet arbre qu'à la n du calcul, sinon
il sera parcouru au fur et à mesure qu'il se construit et le programme ne
s'arrêtera jamais.
1. Créez une page avec quelque chose à parcourir, par exemple la table de
la section 5.2.
2. Introduisez une fonction qui ache les n÷uds dans une fonction d'alerte
en reprenant le code du paragraphe 5.3.4.
3. Dans la fonction de parcours, poussez le nom de chaque n÷ud dans un
tableau initialisé à [ ] au moyen de tableau.push.
4. Introduisez tableau.join("<cr>") dans la balise <div> au moyen de
innerHTML.
5. Créez une belle indentation en introduisant dans la fonction de parcours un deuxième paramètre sous forme de string. Dans l'appel récursif, introduisez " "+strP aram, de sorte qu'à chaque appel, la fonction
90
CHAPITRE 5.
ARBRES DOM ET XML
reçoive une chaîne un peu plus longue. Poussez cette chaîne devant le
nom du n÷ud.
5.5.4 ex - Arbre et tableau associatif
La structure ci-dessous décrit la liste des deux personnes qui ont emprunté
des livres, décrite au paragraphe 5.4.1.
var emprunts =
[
{ personne:
{ identite :
{ prenom: "Peter", nom: "Tagasi" } ,
livres:
[
{ titre: "Tarzan dans les arbres" },
{ titre: "Jane se balance" },
{ titre: "De branche en branche" }
]
}
},
{ personne:
{ identite :
{ prenom: "Hans", nom: "Vogel" } ,
livres:
[
{ titre: "Nid de coucous" }
]
}
}
]
1. Introduisez le texte qui apparaît ci-dessus dans un chier HTML et
essayez d'acher le prénom Hans au moyen de l'instruction de Javascript.
2. Ajoutez un nouveau titre au tableau de titres de Hans Vogel (livres[1]).
Pour cela il faut aecter un deuxième livre (sous la forme d'un tableau
associatif de champ titre) à l'enregistrement de Hans Vogel.
3. Ajoutez l'alerte qui ache le titre de ce nouveau livre à partir de la
structure.
5.5.
EXERCICES
91
5.5.5 ex - Lecture d'une structure XML
On vous propose de lire un chier qui contient la structure vue au paragraphe 5.5.4, sous forme de texte, comme on le ferait dans une application
qui reçoit un objet JSON.
1. Introduisez le texte décrivant la structure de l'exercice précédent dans
un chier xxx au moyen de l'éditeur LemanOS (sans la dénition de la
variable emprunts).
2. Relisez le contenu de ce chier en utilisant responseText, puis introduisez le texte retourné de l'appel emprunts = eval(txt) (La fonction
de lecture est disponible à l'adresse http://site/AJAX). Notez que l'on
pourrait tout aussi bien utiliser File.read
La fonction eval(txt) exécute le contenu de la variable qu'elle reçoit comme
si c'était un programme et la retourne dans la variable à droite du signe
d'aectation. Cela revient donc à entrer le code ci-dessus directement dans
une page HTML. Après cela, vour devriez pouvoir acher les éléments comme
dans l'exercice précédent, sans avoir la structure au chargement de la page
HTML.
5.5.6 ex* - Lecture et sauvetage de données en XML
On vous propose maintenant de sauver les données présentées dans l'exercice précédent dans un chier XML puis de récupérer le contenu de ce chier
et d'acher la liste des noms de personnes récupérées, chacun suivi des titres
des livres que la personne a empruntés.
Pour commencer par le problème le plus facile, on propose de préparer
d'abord un chier XML au moyen de LemanOS, puis de le relire et nalement,
de le créer par programme.
1. Ecrivez le listing ci-dessus dans un chier XML, puis lisez-le dans responseXML. au moyen des fonctions de lecture AJAX.
2. Créez une fonction qui peut écrire dans un chier la structure de l'exercice 5.5.4. Cette fonction doit écrire l'en-tête emprunts, puis avoir une
boucle qui ache une personne à chaque tour. Une deuxième boucle
interne achera les livres.
92
CHAPITRE 5.
ARBRES DOM ET XML
12 août 2010
Chapitre 6
Librairie graphique, SVG
6.1 Introduction
Firefox ore plusieurs possibilités de créer des dessins. Les instructions
de la librairie présentée dans ce chapitre, SVG (scalable vector graphic, ou
graphiques vectoriels extensibles), utilisent la même approche que HTML,
c'est-à-dire que les dessins sont construits au moyen de balises, sur lesquels
les programmes Javascript peuvent agir.
Il est également possible de les construire par programme, en suivant les
explications de la section 6.8.
Les éléments graphiques gèrent à peu près les mêmes événements que les
éléments HTML.
Dans toutes les sections qui suivent, les coordonnées en x et en y sont
mesurées depuis le bord gauche ou depuis le haut de la zone de dessin (gure
6.1).
L'éditeur SVG disponible sur LemanOS est une aide précieuse pour comprendre les paramètres des éléments. Cet éditeur permet de créer des dessins
et de les reprendre dans l'éditeur LemanOS pour introduire les programmes
et les éléments dicilement éditables.
Nous n'avons donné dans ce chapitre que les concepts généraux. Pour des
explications complètes, voir le site :
http ://www.yoyodesign.org/doc/w3c/svg1/#minitoc
6.2 Contenu d'un chier SVG
Comme un chier HTML, un chier dessin.svg contient une structure
standard dans laquelle on peut introduire des balises qui décrivent un dessin
et des instructions Javadscript qui accédent aux éléments du dessin. Cette
93
94
CHAPITRE 6.
LIBRAIRIE GRAPHIQUE, SVG
y
zone du dessin
x
Figure 6.1 Coordonnées
structure suit les règles de XML de façon stricte, ce qui impose quelques
modications des conventions prises pour les chiers HTML.
<svg xmlns="http
://www.w3.org/2000/svg"
xmlns
:xlink="http
://www.w3.org/1999/xlink"
onload="top.svgDocument=document;">
<script type="text/ecmascript">
<![CDATA[
]]>
</script>
</svg>
Pour importer un chier Javascript, (.js), il faut utiliser la commande
ci-dessous, plutôt que celle qui est utilisée dans les pages HTML.
<script type="text/javascript" xlink:href="Calculs.js"/ >
6.3 Cadre SVG dans une page HTML
Il est possible de charger un chier.svg directement dans le navigateur
Firefox, mais on peut également créer une fenêtre qui contient un dessin
SVG dans une page HTML. En positionnant cette fenêtre SVG en absolu au
moyen des styles dans la balise embed, il est même possible de faire apparaître
des éléments graphiques au-dessous d'un texte.
La balise qui crée une telle fenêtre est présentée ci-dessous :
<embed src="xxx.svg" type="image/svg+xml"
width="200px" height="200px"/>
6.4.
95
INSTRUCTIONS SVG
6.4 Instructions SVG
Les instructions de cette section permettent de dénir les éléments usuels
qu'on voit dans les éditeurs graphiques, lignes, rectangles, textes, etc.
6.4.1 Ligne
L'élément line dessine un segment de droite.
<line x1="5" y1="5" x2="45" y2="45"
id="monId" stroke="black"/>
6.4.2 Cercle
L'élément circle spécie un cercle par son centre et son rayon.
<circle cx="25" cy="25" r="20"
stroke="black" fill="white"/>
y
6.4.3 Ellipse
x
L'ellipse est déterminée par son centre et les rayons sur les axes horizontal
et vertical.
<ellipse cx="25" cy="25" rx="20" ry="10"
stroke="black" fill="white"/>
6.4.4 Tracés
x
Le tracé est un élément plus complexe qui permet de dénir des courbes
comprenant des segments de droites, des portions de cercles et des courbes
de Bézier.
Les points doivent être séparés par des espaces. Les coordonnées d'un
point doivent être séparés par des espaces ou par des virgules.
<path d="M5,5 C5,45 45,45 45,5"
stroke="black" fill="none"/>
y
x
Le tracé est décrit par une suite de lettres et de nombres qui déterminent
les segments de courbe. Les lettres ont les signications décrites ci-dessous.
Pour les utiliser, vous avez intérêt à utiliser l'éditeur graphique disponible
dans l'environnement LemanOS.
y
zone
x
top.fctSvg=function() {…}
programme
dans SVG
96
CHAPITRE 6.
LIBRAIRIE GRAPHIQUE, SVG
Figure 6.2 Points de dénition de tangentes symmétriques
M la courbe saute au point dont les coordonnées x et y sont données par les
deux nombres qui suivent sans tracer de courbe. Cette commande est
utilisée entre autres pour dénir le premier point de la courbe
L un trait rectiligne est tiré du dernier point déni au point dont les coordonnées x et y sont données par les deux nombres qui suivent
H tire un trait horizontal depuis le dernier point au point dont la coordonnée
horizontale est donnée par le nombre qui suit
V tire un trait vertical depuis le dernier point au point dont la coordonnée
verticale est donnée par le nombre qui suit
C suivi de x1, y1, x2, y2, x, y tire une courbe de Bézier cubique depuis le
dernier point jusqu'au point x, y ; les points x1, y1 et x2, y2 dénissent
les tangentes au dernier, respectivement prochain point
S semblable à C, mais ne prend pas de point x1, y1, mais utilise à la place, le
point symétrique de la dernière tangente par rapport au dernier point
(Fig. 6.2)
Q suivi de x1, y1, x, y tire une courbe de Bézier quadratique depuis le dernier
point jusqu'au point x, y ; le point x1, y1 dénit les tangentes au dernier,
respectivement prochain point ; ces tangentes se croisent donc en x1,
y1
T ne prend pas de point x1, y1, mais utilise à la place, le point symétrique
de la dernière tangente par rapport au dernier point (Fig. 6.2)
Z termine la courbe sur le premier point
A dénit des arcs de cercles (voir la documentation ocielle)
6.4.5 Polygone (fermé)
Le polygone est constitué d'une liste de points qui forment les points
successifs reliés par des segments de droite. Le dernier point est automatiquement relié au premier. Il est possible de peindre l'intérieur du polygone,
6.4.
97
INSTRUCTIONS SVG
même si des segments du polygone se croisent. Pour voir les règles, voir la
documentation ocielle ou utiliser l'éditeur SVG de LemanOS.
<polygon points="5,5 45,45 5,45 45,5"
stroke="black" fill="none"/>
6.4.6 Ligne polygonale (ouverte)
Cet élément est similaire au précédent, mais le dernier point n'est pas relié
au premier. Il est également possible de le remplir. Le remplissage s'arrête
au segment de droite (invisible) qui passe du premier au derniery point.
<polyline points="5,5 45,45 5,45 45,5"
stroke="black" fill="white"/>
zone du dessin
x
6.4.7 Rectangle
Cette instruction dessine un rectangle dont on donne le point supérieur
gauche, la largeur et la longueur.
<rect x="5" y="5" width="40" height="40"
stroke="black" fill="none"/>
6.4.8 Rectangle arrondi
y
zone du dessin
x
Cet élément est similaire au précédent, mais il a un argument supplémentaire qui dénit les rayons d'arrondi des coins, suivant les axes x ou y.
y
<rect x="5" y="5" rx="5" ry="5" width="40" height="40"
stroke="black" fill="black"/>
6.4.9 Image
zone du dessin
x
Il est possible d'ajouter une image dans le dessin au moyen de l'instruction suivante. Les paramètres sont les mêmes que pour un rectangle. Si l'on
ne dénit que la largeur ou que layhauteur, l'autre dimension est mise automatiquement en proportion.
<image x="200" y="50" width="143" height="42" zone du dessin
x ://lti.epfl.ch//titre.png"/>
xlink
:href="http
98
CHAPITRE 6.
LIBRAIRIE GRAPHIQUE, SVG
6.4.10 Texte
Les instructions suivantes dénissent des segments de texte. Ces derniers
ne peuvent pas contenir de retour de ligne. Pour commencer une nouvelle
ligne, il faut introduire un seconde instruction et calculer soi-même le déplacement vertical.
<text x="200" y="50" >
</text>
Un texte
<text x="200" y="50" text-anchor="start"
font-family="Verdana" font-size="55"
font-style="italique" font-weight="bold"
fill="red">
</text>
Un texte
L'attribut text-anchor indique quel point du texte est placé aux coordonnées x ;y. Les indications start, middle et end sont possibles. Si vous ne
dénissez rien, le point gauche du texte start sera placé en x ;y.
Contrairement à l'instruction précédente, l'instruction suivante dénit
tous les paramètres du texte dans un attribut style.
<text x="200" y="50" style="text-anchor: end;
font-family: Verdana; font-size: 55;">
</text>
Un texte
6.4.11 Texte le long d'un tracé
Les possibilités de traiter les textes sont innombrables et il faut vous
référer à la documentation complète citée dans la première section pour les
utiliser. Nous donnons toutefois un exemple ci-dessous :
<path id="textPath" d="M10 50 C10 0 90 0 90 50"/></defs>
<text fill="red">
<textPath xlink:href="#textPath">Aaaa Bbbb Cbbb</textPath>
</text>
Ce code va écrire le texte le long d'un tracé, tel qu'il a été vu au paragraphe 6.4.4.
6.5.
TRANSFORMATIONS
99
6.5 Transformations
Ainsi qu'on l'a mentionné, les éléments (et les groupes) des dessins SVG
peuvent subir des transformations avant leur apparition. Par exemple, le
rectangle suivant sera positionné en (50;50), puis tourné de 30 degrés autour
du point (80;80) avant d'être rendu visible.
<rect x="50" y="50" width="40" height="40"
ll="red" transform="rotate(30,80,80)"/>
Ces transformations sont basées sur les multiplications matricielles, dont
on donne la forme pour chaque transformation. On utilise les coordonnées
généralisées pour pouvoir exprimer toutes les transformations ci-dessous par
un produit matriciel et ainsi créer une matrice représentant plusieurs transformations simplement en les multipliant entre elles. Comme il arrive souvent
qu'il faille eectuer la même transformation sur plusieurs points, cela accélère
les calculs.
Voici la liste des transformations :
rotate(30, 80, 100) dénit une rotation de 30 degrés autour du point
(80;100). Si les deux derniers paramètres sont absents, la rotation s'effectue autour de l'origine des axes.
 
  
cos a − sin a 0
x0
x
 y 0  =  sin a cos a 0  ·  y 
1
0
0
1
1

translate(100, 110) décrit une translation (déplacement) de (100;110) points.
Si le deuxième paramètre n'est pas introduit, il est considéré être 0 et
la translation est faite selon l'axe x.
 
  
1 0 tx
x
x0
 y 0  =  0 1 ty  ·  y 
1
0 0 1
1

scale(2, 3) étend l'élément (ou le groupe) de 2 fois horizontalement et de 3
fois verticalement. Si le deuxième paramètre n'est pas déni, les agrandissements sur les deux axes sont les mêmes et correspondent au paramètre unique.
 
  
x0
sx 0 0
x
 y 0  =  0 sy 0  ·  y 
1
0 0 1
1

100
CHAPITRE 6.
LIBRAIRIE GRAPHIQUE, SVG
skewX(45) penche le dessin de 45 degrés selon l'axe x, c'est-à-dire que les
points proches du haut du dessin ne sont que peu déplacés sur la droite
et plus les points en sont éloignés, plus ils sont déplacés. Le paramètre
indique l'angle formés par ces déplacements.
 
  
x0
1 tan a 0
x
 y0  =  0
1
0 · y 
1
0
0
1
1

skewY(30) comme ci-dessus, mais pour l'autre axe.
 
  
x0
1
0 0
x
 y 0  =  tan a 1 0  ·  y 
1
0
0 1
1

Il est possible de mettre plusieurs transformations à la suite, séparées par
des espaces (ou des virgules) dans l'attribut transform. Dans ce cas, c'est la
dernière transformation qui est exécutée la première.
6.6 Dénitions d'éléments et de groupes
Le bloc ci-dessous constitue un bloc de dénition. Les éléments et groupes
dénis dans ce bloc n'apparaissent que s'il sont explicitement appelés par une
instruction use telle que celle qui apparaît à la n de ce paragraphe.
<defs>
groupes . . . (dans n'importe quel ordre)
éléments . . .
<g id='group1' fill='green' transform='scale(0.5) rotate(30)'>
éléments . . .
</g>
</defs>
Le groupe déni ci-dessus peut être aché par la commande suivante. Il
sera aché à la position dénie ci-dessous.
<use x='240' y='5' xlink:href='#group1'/>
Si l'on avait ajouté des coordonnées dans la dénition, le rectangle serait
positionné à un point déni par la somme des coordonnées de la dénition
et de l'appel.
6.7.
DÉGRADÉS DE COULEURS
101
Figure 6.3 Positionnement d'un dégradé
6.7 Dégradés de couleurs
6.7.1 Dégradés linéaires
Le programme ci-dessous dénit un gradient et l'utilise dans un cercle
(gure 6.3).
<defs>
<linearGradient id='unGradient'
x1="20%" y1="0%" x2="100%" y2="50%">
<stop oset="20%"
style="stop-color: rgb(255,255,0); stop-opacity: 1"/>
<stop oset="80%"
style="stop-color: rgb(255,0,0); stop-opacity: 1"/>
</linearGradient>
</defs>
<ellipse cx="200" cy="90" rx="80" ry="80"
style="ll:url(#unGradient)"/>
Ce gradient est déni dans une paire de balises <defs>. Il crée un dégradé
linéaire (pas concentrique). Les lignes de même intensité sont perpendiculaires à un vecteur qui part de (x1;y1) et aboutit à (x2;y2). Ces vecteurs sont
calculés par rapport à un rectangle qui englobe le dessin. Ainsi (0%;0%) à
(100%;50%) représente un vecteur qui va du coin en haut à gauche (rappel :
c'est là que se trouve l'origine des axes) et qui se termine au milieu du côté
droit du dessin. Notez que ces règles sont approximatives.
102
CHAPITRE 6.
LIBRAIRIE GRAPHIQUE, SVG
Les balises linearGradient contiennent deux instructions stop. Ces stops
indiquent la couleur à placer à diérents points du vecteur susmentionné. Le
premier stop indique que la couleur à placer sur la perpendiculaire placée à
20% du début de vecteur est rgb(255,255,0). Le deuxième stop est placé près
de l'autre extrémité du vecteur (80%) et la couleur est du rouge pur.
La couleur varie continûment d'un stop à l'autre. A l'extérieur des stops,
la couleur est celle du stop le plus proche.
Le fait que l'ellipse doit être peinte avec un gradient donné est indiqué
au moyen d'un identicateur dans la dénition et de url() dans l'ellipse :
fill:url(#unGradient)
pas de guillemets dans les parenthèses, mais le signe #, pour indiquer qu'on
se trouve dans la même page.
6.7.2 Dégradés radiaux
Ce gradient crée un gradient concentrique. Le gradient part du point
(focale) fx et fy exprimé en pourcentage du rectangle englobant. Il se prolonge
jusqu'à un cercle centré en cx;cy et de rayon r.
<defs>
<radialGradient id="unGradient" cx="40%" cy="20%" r="30%"
fx="40%" fy="20%">
<stop oset="20%" stop-color="rgb(255,255,0)" stop-opacity="1"/>
<stop oset="80%" stop-color="rgb(255,0,0)" stop-opacity="1"/>
</radialGradient>
</defs>
<ellipse cx="200" cy="90" rx="80" ry="80"
style="ll :url(#unGradient)"/>
En fait si l'on donne à tous les paramètres fx, fy, cx, cy et r la valeur de
50%, et que les stops se trouvent en 0% et 100% respectivement, le dégradé
partira du centre de l'élément recevant le dégradé et se prolongera jusqu'aux
bords de l'élément (s'il est de mêmes dimensions horizontale et verticale).
6.8 Accès aux éléments en Javascript
6.8.1 Créations d'éléments
Pour créer un élément, il faut utiliser la série d'instructions ci-dessous,
de façon très semblable à ce qui était utilisé pour les éléments HTML. Par
6.9.
COMMUNICATIONS ENTRE HTML ET SVG
103
contre, pour accéder aux attributs, il faut maintenant utiliser une fonction.
Les coordonnées n'ont par contre pas d'unités, par défaut elles correspondent
aux pixels.
var shape = document.createElementNS(
"http://www.w3.org/2000/svg", "circle")
shape.setAttribute("id", "monCercle")
shape.setAttribute("cx", 150)
shape.setAttribute("fill", "green")
document.documentElement.appendChild(shape)
Les textes ont un enfant supplémentaire, qu'il faut préparer de la façon
indiquée ci-dessous :
var shape = document.createElementNS(
"http://www.w3.org/2000/svg", "text")
document.documentElement.appendChild(shape)
shape.firstChild.data = "NouveauTexte"
6.8.2 Modication des éléments
Les éléments peuvent se trouver au moyen du même appel, getElement-
ById, que celui que l'on utilise pour HTML :
document.getElementById("monCircle").setAttribute("cy", 190)
6.9 Communications entre HTML et SVG
Si l'on inclut une zone SVG dans une page HTML, au moyen de la balise
<embed>, on peut accéder aux variables de la zone SVG depuis la page
HTML et vice-versa, mais il faut prendre quelques précautions.
La gure 6.4 représente les environnements des programmes de la page
HTML et ceux de la zone de dessin de SVG.
Les programmes habituels de la page HTML sont accessibles soit directement, comme d'habitude, soit par l'attribut top, déni automatiquement
dans toute page HTML.
Par contre dans les programmes de la zone SVG, seul top est connu. SVG
doit donc ajouter top. devant les appels aux fonctions et devant les noms des
variables de la page HTML pour y accéder.
La page HTML n'a accès ni aux fonctions ni aux variables de la zone
SVG. Si une entité de la zone de SVG doit être accédée par un programme
de la page HTML, ils doivent être dénis dans la zone de la page en ajoutant
également top. devant.
104
CHAPITRE 6.
LIBRAIRIE GRAPHIQUE, SVG
top
function fctHtml() {}
fctSvg()
top.fctHtml();
top.fctSvg=function() {…}
programme
dans SVG
programme
dans HTML
Figure 6.4 Relation entre une page HTML et SVG
Si un programme de SVG dénit une fonction en utilisant top.f, il doit y
accéder lui-même au moyen de top.f().
Ainsi, les variables varHtml, varSvg et les fonctions fctHtml et fctSvg sont
accessibles depuis HTML (à gauche) et depuis SVG (à droite) :
HTML
SVG
var varHtml = 99
function fctHtml() {...}
var x = top.varHtml
top.fctHtml()
var y = varSvg
fctSvg()
top.varSvg = 88
top.fctSvg = function() {...}
Les dénitions des variables après top ne doivent pas être précédées par
var, car top est une entitée déjà dénie. Pour la même raison, il faut dénir
la fonction de la façon spéciale présentée ci-dessus. En fait, la dénition fct
= function() {...} génère tout-à-fait la même chose que function fct() {...},
mais dans le cas présenté dans ce paragraphe seule la première forme est
utilisable.
Pour créer un élément SVG par programme depuis la page HTML, il faut
atteindre l'attribut document de l'environnement SVG. Pour cela il sut
d'aecter, à l'intérieur de l'environnement SVG, la référence du document
local à une variable dénie dans top, par exemple en plaçant
top.svgDocument = document
dans l'attribut onload de la balise <svg>. En eet, à l'intérieur de la zone
SVG, document est celui qui est interne à la zone.
On peut alors utiliser les instructions de création d'éléments vues précécemment directement depuis la page HTML.
6.10.
EXERCICES
105
var shape = svgDocument.createElementNS(
"http://www.w3.org/2000/svg", "circle")
shape.setAttribute("cx", 150)
svgDocument.documentElement.appendChild(shape)
6.10 Exercices
Note : pour que le dévermineur charge correctement les chiers .svg, il faut
introduire le paramètre image/svg+xml dans l'attribut extensions.rebug.cache.mimeTypes de la liste qui apparaît en tapant about:cong dans le champ
d'adresse du navigateur Firefox.
6.10.1 ex - Battement de c÷ur
Dans cet exercice, on vous propose de dessiner un c÷ur qui bat. Cet exercice peut être fait soit sur l'éditeur SVG, soit dans LemanOS. Mais le coeur
est évidemment plus facile à dessiner dans l'éditeur SVG et peut être facilement récupérédans LemanOS. Le code de cet exercice est d'ailleurs visible
dans la documentation de l'éditeur SVG (bouton en haut à gauche).
1. Utlisez l'éditeur SVG disponible dans l'environnement et dessinez l'image
d'un c÷ur, au moyen d'une courbe de Bézier, et sauvez le chier dans
LemanOS.
2. Créez une fonction qui incrémente la valeur d'une variable globale à
chaque appel et qui la remet à 1 lorsqu'elle arrive à 20.
3. A la n de la fonction, introduisez une instruction qui met l'épaisseur
(stroke-width) du trait de la courbe de Bézier à la valeur de la variable
globale.
4. Appelez cette fonction au moyen de setInterval (voir section 2.21).
6.10.2 ex - Editeur simple
Faisons un petit éditeur de dessin. Lorsque l'utilisateur enfoncera le bouton de la souris, la fonction appelée mémorisera le point cliqué dans une
variable globale et quand il relâchera le bouton, une autre fonction générera une ligne partant du premier point et aboutissant au point déni par
l'événement.
On peut compléter le programme en achant quelques éléments (un carré,
un rectangle, un cercle, etc) et en mettant dans l'attribut onmouseup de
ces éléments un appel à une fonction qui mémorise un numéro identiant
106
CHAPITRE 6.
LIBRAIRIE GRAPHIQUE, SVG
l'élément cliqué dans un variable globale, de sorte que lorsque l'utilisateur
clique pour dessiner, la fonction correspondante pourra générer un élément
du type correspondant à l'identité mémorisée dans la variable globale.
Il faut encore prendre la précaution suivante. Lorsque l'on clique sur
l'élément, il ne faut pas que le clic appelle aussi la fonction de dessin, sinon le premier point serait toujours sous l'élément sélectionné. Pour empêcher que l'événement ne soit plus utilisé par d'autres éléments après avoir
été détecté dans une fonction, il sut d'appeler la commande suivante :
evt.stopPropagation(), où evt est l'événement passé dans la fonction (paragraphe 3.2.2).
Pour faire un éditeur complet, il faut mémoriser les paramètres de chaque
élément dessiné en plus de le dessiner. C'est ce qui est fait dans l'éditeur SVG.
6.10.3 ex - Feux de carrefour
A partir d'une photo d'un poteau portant des feux pour piétons 1 , on va
créer une simulation du fonctionnement de ces feux. Cet exercice peut être
fait soit dans l'éditeur SVG, soit dans LemanOS.
1. Mettez l'adresse http ://lti.ep.ch/Livre/images/feux.jpg dans le champ
de l'URL de l'image de l'éditeur SVG et cliquez le bouton image. Vous
pouvez réduire la taille de l'image en changeant ses attributs width et
height ( les proportions de l'image ne changent pas, mais elle est réduite
de la valeur des deux attributs qui a la plus grande réduction ).
2. Couvrez les trois feux par trois cercles et ajustez les attributs si vous
voulez les aligner.
3. Ajoutez l'image d'adresse http ://lti.ep.ch/Livre/images/boutonFeu.jpg
sur le poteau des feux.
4. Appelez une fonction périodiquement au moyen de setInterval.
5. Dénissez une variable globale, par exemple couleur et utilisez-là dans
un switch dans la fonction que vous venez de dénir. Le premier cas du
switch sera exécuté si couleur est "red", le deuxième si elle est "green",
le troisième si elle est "orange" et le dernier est appelé par défaut.
6. Chaque cas met son feu (rouge, orange ou vert) à la couleur du cas et
les autres à noir. Le cas "red" change ensuite la variable couleur à la
chaîne vide, le cas "green" à "orange" et le troisième à "red". Ainsi,
si la variable globale est mise à "green", le feu vert sera allumé. Au
prochain appel l'orange sera allumé, puis le rouge. Les appels suivants
laisseront la position rouge, car ils passent par le défaut.
1. En fait la photo montre les feux des voitures, désolé !
6.10.
EXERCICES
107
7. Ajoutez un appel à une nouvelle fonction qui met la variable couleur à
"green" dans l'attribut onclick de l'image du bouton du piéton et qui
déclenche ainsi les diérentes phases du feu.
Sauvez le programme, cliquez Exécution puis cliquez le bouton qui demande le passage sur le dessin. Vous pouvez ajouter des cas et/ou des compteurs au switch pour allonger les phases de certaines couleurs.
6.10.4 ex* - Représentations 3-D
Ce qu'on voit sur un écran a forcément deux dimensions. Si l'on représente
un objet 3-D, il faut lui faire subir une rotation qui amène l'axe depuis
lequel on le regarde perpendiculaire à l'écran et utiliser les deux dimensions
devenues parallèles à l'écran pour les coordonnées des éléments qu'on dessine.
La troisième dimension n'est plus utile que pour déterminer quelles sont les
parties cachées.
Dans cet exercice, on va créer des composants et des librairies grâce auxquels on aboutira à une représentation de mouvements 3-D. Les parties de
cet exercices forment en elle-mêmes des problèmes intéressants qui pourraient
être répartis parmi les membres d'un groupe.
Curseurs
Il est souvent utile d'avoir des curseurs grâce auxquels on peut faire varier
des valeurs utilisées dans le programme, par exemple pour commander des
rotations ou des translations.
Pour qu'on puisse traiter facilement plusieurs curseurs sur le même dessin,
on va regrouper les variables d'un curseur en les mettant dans un objet (ou
tableau associatif, voir paragraphe 5.3.5) :
var curseurX = {id:"curseurX", valeur:0}
Ainsi pour obtenir l'identicateur du curseur, on utilisera curseurX.id, pour
la valeur, curseurX.valeur, etc. Ci-dessous, on va préparer un curseur représenté par un petit carré qu'on déplace au moyen de la souris et dont la
position fournit une valeur utile au programme.
1. Créez un objet, comme curseurX ci-dessus, auquel vous ajouterez les
paramètres du curseur au fur et à mesure qu'on les déterminera.
2. Créez une variable globale curseurActif dans laquelle on copiera le curseur que l'utilisateur clique pour le sélectionner.
3. Créez un petit carré pour représenter le curseur sur l'écran et ajoutez-y
un attribut onmousedown qui contient une instruction. Cette dernière
108
CHAPITRE 6.
LIBRAIRIE GRAPHIQUE, SVG
dépose dans curseurActif l'objet qui représente le curseur qu'on est en
train de cliquer.
Dénissez un attribut onmouseup dans la balise svg. Cet attribut
contient une instruction qui remet à nul curseurActif.
Ainsi chaque fois que l'utilisateur a sélectionné un curseur, les paramètres de ce curseur sont automatiquement disponibles dans curseurActif, mais quand il l'a relâché, le programme sait qu'aucun curseur
n'est sélectionné.
4. Dans la balise svg, dénissez un attribut onmousemove qui appelle la
fonction qui gère le curseur s'il y en a un d'actif.
Cette fonction retourne sans rien faire si la variable globale curseurActif
est aectée à nul.
Sinon, elle met la valeur de evt.clientX dans l'attribut valeur de ce
curseur (curseurActif.valeur).
Puis elle place le carré à la même position que le curseur en le recherchant au moyen de l'identicateur placé dans l'objet du curseur
actuellement dans curseurActif.
5. Vériez que si vous mettez plusieurs carrés avec les caractéristiques
ci-dessus, l'un ou l'autre des curseurs bouge quand on les clique.
6. Ajoutez un attribut, dans l'objet contenant les paramètres du curseur,
qui indique si le curseur est horizontal ou vertical et adaptez la fonction
appelée par onmousemove pour qu'elle lise clientX et positionne le carré
en x si le curseur est horizontal ou les valeurs correspondantes en y si
le curseur est vertical. Revériez votre nouvelle version.
7. Ajouter des cadres dans lesquels les carrés représentant les curseurs se
déplacent.
8. Ajoutez les limites acceptées pour clientX ou clientY dans les objets
contenant les paramètres des curseurs et empêchez le carré du curseur
de partir au-délà de ces limites.
9. Si le curseur se déplace par exemple de 5 à 795, on désire souvent que
ce déplacement représente une plage de valeurs signicatives pour le
programme, par exemple -1 à +1. Mettez les limites valables dans le
programme et convertissez-les dans la fonction appelée par onmousemove 2 .
2. Faites un calcul qui équivaut à déplacer l'intervalle correspondant au curseur à 0,
réduire cet intervalle à la longueur 1, puis l'étirer à la longueur de l'intervalle demandé et
nalement le décaler de l'origine de l'intervalle du programme. Ces transformations sont
à appliquer à chaque valeur reçue de clientX
6.10.
EXERCICES
109
Matrices, vecteurs
Pour eectuer les rotations ou les translations, on utilisera le même concept
que pour les deux dimensions (paragraphe 6.5). Il faut donc préparer un certain nombre de fonctions mathématiques pour cela. Vous pouvez écrire ces
fonctions dans un chier .js (paragraphe 6.2)
1. Créez une fonction qui prend une matrice (tableau à deux dimensions)
et un vecteur et qui retourne le vecteur représentant le produit de
la matrice par le vecteur. Cette fonction nécessite deux boucles for
imbriquées.
2. Créez une fonction qui prend deux matrices et qui retourne le produit
des deux matrices. Cette fonction nécessite trois boucles for imbriquées.
3. Créez trois fonctions, pour les axes x, y et z, qui prennent un angle et
qui créent les matrices produisant les rotations autour de chacun des
axes.
4. Créez une fonction qui retourne la matrice eectuant une translation
de x, y, ou z.
Achage
Préparez une fonction qui convertit les 4 coordonnées 3 d'un point du
modèle en celles de l'écran. Par exemple si vous avez choisi le milieu de l'écran
en 400;400, l'axe x de gauche à droite et l'axe z vers le haut de l'écran, vous
aurez les conversions :
xecran = x + 400
yecran = 400 − y
Vous pourriez également mettre le dessin à une échelle donnée à cet endroit.
Représentation d'un point qui tourne autour de l'axe z
On a maintenant tous les éléments pour représenter la rotation d'un point
[100, 100, 100, 1] qui tourne autour de l'axe z, par exemple.
1. Dans la fonction appelée lorsqu'on déplace un curseur, demandez la
génération d'une matrice de rotation en fonction de la valeur du curseur
(le curseur représentera un intervalle −π; +π en passant d'un bout à
l'autre) puis appelez une fonction qui multiplie les matrices de rotation
générées par les curseurs (horizontal et vertical), multiplie le point par
la matrice résultante et ache ce point.
3. N'oubliez pas le 1 des coordonnées généralisées
110
CHAPITRE 6.
LIBRAIRIE GRAPHIQUE, SVG
p
C
B
b
a
A
Figure 6.5 Orientation des vecteurs
Représentation d'un solide
Si l'on répète ce procédé sur plusieurs points, on peut représenter un solide
en utilisant les points pour dessiner des polygones. Pour simplier le code, il
faut stocker les points dans un tableau et les transformer tous ensemble.
Chaque polygone sera représenté par son propre tableau d'index pointant
sur les points du solide. Entrez les points dans le sens des aiguilles de la
montre, lorsque l'on regarde la face de l'extérieur (utile par la suite pour la
coloration).
1. Créez un tableau contenant les points du solide que vous voulez représenter.
2. Créez une fonction qui reçoit en paramètre la liste des index des points
formant une face du solide et qui retourne la chaîne à placer dans
l'attribut points d'un polygone SVG.
3. Achez les polygones en ne motrant d'abord que leur contour, stroke
Pour acher ces faces en couleur, il faut savoir quelle face est visible. Vous
pouvez pour cela calculer le vecteur perpendiculaire à la surface (produit
vectoriel). Si ce vecteur pointe vers l'avant (coordonnées y < 0), on doit
colorer le polygone de la face, en supposant que le solide n'a pas de face
rentrante.
Attention à l'orientation des axes et des vecteurs. Il faut utiliser la règle
du tire-bouchon (gure 6.5) : quand on tourne le tire-bouchon, il sort de la
face.
Quand on eectue une rotation autour de l'axe z , qui fait venir l'axe x
sur l'axe y , le tire-bouchon avance vers le côté positif de z . De même pour
tous les axes :
Rotation autour de fait venir sur et le tire-bouchon avance sur
x
y
z
y
z
x
z
x
y
x
y
z
6.10.
111
EXERCICES
Le produit vectoriel p~ = ~a ∗ ~b crée un vecteur perpendiculaire aux deux
membres, ~a et ~b du produit. Son calcul est donné ci-dessous


a1 ∗ b2 − a2 ∗ b1
p~ =  a2 ∗ bo − a0 ∗ b2 
ao ∗ b 1 − a1 ∗ b 0
Vous pouvez maintenant créer un cube ou un tétraèdre, représenter les
arrêtes par des lignes et le faire tourner selon les axes x, y ou z.
112
CHAPITRE 6.
LIBRAIRIE GRAPHIQUE, SVG
Chapitre 7
Librairie graphique : Canvas
7.1 Introduction
<canvas> est un élément HTML qui peut être utilisé pour produire des
éléments graphiques sur une page web, en utilisant javascript. Par exemple,
il peut être utilisé pour dessiner des graphiques, faire des compositions de
photos ou encore faire des animations. La balise <canvas> est donc
un moyen de créez des dessins sur une page web
une balise, à écrire dans le corps (<body>) d'une page
7.2 Les attributs de <canvas>
La balise <canvas> prend généralement les attributs suivants :
id : un identiant qui nous permettra, par la suite, d'accéder à la balise
pour y produire le dessin
width : la largeur de la surface de dessin
height : la hauteur de la surface de dessin
Nous avons donc la syntaxe suivante :
<canvas id='...' width='...' height='...'></canvas>
7.3 Intitialiser <canvas>
7.3.1 document.getElementById(...)
La création d'un dessin se fait à l'aide d'instructions javascript, écrites
dans la partie head d'une page HTML. A l'aide de document.getElementById(...)
113
114
CHAPITRE 7.
LIBRAIRIE GRAPHIQUE : CANVAS
nous pouvons accéder au canvas. Voici donc les premières étapes pour produire un dessin sur une page à l'aide de <canvas> :
<html
<head>
<script>
document.getElementById('monCanvas')
</script>
</head>
<body>
<canvas id='monCanvas' width='200px' height='200px'>
</canvas>
<body>
</html>
7.3.2 getContext('2d')
La fonction getContext('2d') permet d'obtenir une variable, que nous
allons appeler ctx (abréviation pour contexte). Ce contexte contient les fonctions de canvas moveTo(...), lineTo(...), etc...
<html
<head>
<script>
var ctx
document.getElementById('monCanvas').getContext('2d')
</script>
</head>
<body>
<canvas id='monCanvas' width='200px' height='200px'>
</canvas>
<body>
</html>
7.3.3 Fonction d'initialisation
Il est plus judicieux de mettre les instructions ci-dessus dans une fonction,
que nous appellerons initialiser() et qui sera appelée au chargement de la
page <body onload='initialiser()'>
7.3.
INTITIALISER <CANVAS>
115
<html
<head>
<script>
var ctx
initialiser(){
document.getElementById('monCanvas').getContext('2d')
function
}
</script>
</head>
<body onload='initialiser()'>
<canvas id='monCanvas' width='200px' height='200px'>
</canvas>
<body>
</html>
7.3.4 Fonction de dessin
L'objet ctx contient les méthodes pour créer des dessins, comme moveTo(...), lineTo(...), etc... Dans une fonction (nommée dessiner() ci-dessous),
nous pouvons écrire les instructions de dessin (qui, en l'occurence dessinent un triangle rectangle).
<html
<head>
<script>
var ctx
initialiser(){
document.getElementById('monCanvas').getContext('2d')
function
}
function
dessiner(){
ctx.moveTo(100,100)
ctx.lineTo(100,50)
ctx.lineTo(50,100)
ctx.lineTo(100,100)
}
</script>
</head>
<body onload='initialiser();dessiner()'>
<canvas id='monCanvas' width='200px' height='200px'>
</canvas>
116
CHAPITRE 7.
LIBRAIRIE GRAPHIQUE : CANVAS
<body>
</html>
7.3.5 Notion de chemin
Pour que le dessin s'ache, il est nécessaire de dénir un chemin (path),
à l'aide de beginPath(). Une fois le chemin créé, on utilise stroke() le
contour du chemin, ou ll() pour remplir la forme.
<html
<head>
<script>
var ctx
initialiser(){
document.getElementById('monCanvas').getContext('2d')
function
}
function
dessiner(){
ctx.beginPath()
ctx.moveTo(100,100)
ctx.lineTo(100,50)
ctx.lineTo(50,100)
ctx.lineTo(100,100)
ctx.stroke()
}
</script>
</head>
<body onload='initialiser();dessiner()'>
<canvas id='monCanvas' width='200px' height='200px'>
</canvas>
<body>
</html>
7.4.
RÉSUMÉ SUR L'INITIALISATION DE <CANVAS>
117
7.3.6 Résultat
En exécutant les instructions de la section précédente, nous obtenons la gure ci-contre
7.4 Résumé sur l'initialisation
de <canvas>
Sur la gure 7.4, nous présentons encore une fois,
sous une forme résumée, les instructions d'initialisation de <canvas> :
Figure 7.1 Résultat
<canvas>
A : La balise <canvas>
Mettre, dans le corps de la page, une balise <canvas> avec les attributs
suivants :
l'identiant (id) donne un nom à l'élément, ce qui permet d'y accéder
la largeur (width)
la hauteur (height)
B : La fonction qui initialise la surface de dessin
Dans un script, écrit dans la partie head du document HTML un fonction
permet d'initialiser la surface de dessin. Voir partie B de la gure 7.4
C : L'appel de la fonction d'initialisation
A l'aide d'un événement, appler la fonction d'initialisation. Voir partie C
de la gure 7.4
7.5 Les images
<canvas> nous donne la possibilité de dessiner des images. Ceci peut par
exemple être utilisé pour faire un album de photos, mettre un arrière-plan à
un graphique, etc... L'importation d'une image dans canvas est composée de
2 étapes :
1. la création de l'image (dont on connait l'url)
2. le dessin de l'image dans canvas
118
CHAPITRE 7.
LIBRAIRIE GRAPHIQUE : CANVAS
Figure 7.2 Résumé de l'initialisation de <canvas>
7.5.1 Création d'une image
Une façon de créer une image est d'en faire un nouvel objet, puis de faire
correspondre son attribut source (src) à l'url de l'image que nous voulons
acher :
var img = new Image() ; // Création de l'objet 'image'
img.src = 'myImage.png' ; // Faire correspondre sa source à l'adresse d'une image.
7.5.2 Dessin d'une image dans <canvas>
Une fois l'image créée, la fonction
drawImage(image,x,y)
permet de dessiner l'image dans le canvas, en plaçant le coin supérieur
gauche de l'image aux coordonnées (x,y).
7.6 Transformations géométriques
7.6.1 Introduction
Canvas ore des méthodes permettant d'appliquer des changements au
référentiel (le système d'axes) pour eectuer
7.6.
TRANSFORMATIONS GÉOMÉTRIQUES
119
des changements d'échelle
des rotations
des translations
7.6.2 Changement d'échelle : scale(x,y)
La fonction scale(x,y permet de changer l'échelle (i.e faire un zoom ).
x correspond au changement d'échelle suivant l'axe horizontal, tandis que y
correspond au changement d'échelle suivant l'axe vertical. Les 2 paramètres
doivent être des nombres entiers ; les nombres inférieurs à 1 correspondent à
un rétrécissement, tandis que les nombres supérieurs à 1 correspondent à un
aggrandissement.
7.6.3 Translation : translate(dx,dy)
La fonction translate(dx,dy) permet de déplacer le référentiel du canvas
d'une distance horizontale dx et d'une distance verticale dy. Il est important
de noter que, à la diérence de SVG, c'est le référentiel, et non les objets qui
se déplacent.
7.6.4 Rotation : rotate(angle)
La méthode rotate(angle) permet de tourner le référentiel, autour de
l'origine. Un angle positif correspond à une rotation dans le sens des aiguilles d'une montre. Pour faire une rotation autour d'un autre point, on
fera d'abord une translation (à l'aide de la méthode translate(dx,dy) ), puis
la rotation.
120
CHAPITRE 7.
LIBRAIRIE GRAPHIQUE : CANVAS
12 août 2010
Chapitre 8
Objets
8.1 Concept de base
Les éléments de HTML ou de SVG que nous avons vus dans les chapitres
précédents sont des objets. Ils possèdent des attributs et dénissent des
fonctions qu'on peut appeler en utilisant la référence de l'objet :
var monImage = document.getElementById("image")
monImage.width = 15
document est un objet qui dénit la fonction getElementById, monImage
est un objet qui possède l'attribut width. Pour accéder à l'attribut ou à la
fonction d'un objet, on les sépare du nom de l'objet par un point . .
Des objets ont également été vus dans la section 5.3.5.
La programmation qui recourt aux objets est dite orientée objets. La
référence ou la variable qui repère un objet est appelée pointeur de l'objet. Un objet n'est pas stocké dans la variable, mais dans une partie de la
mémoire du programme réservé à cet eet. La variable ne contient qu'une
référence à l'objet. Cela n'est pas directement visible dans un programme,
mais a des conséquences, comme le décrit le paragraphe 8.2.
L'avantage de la programmation orientée objets réside dans l'encapsulation des informations. Tous les détails des programmes sont cachés dans
les dénitions des objets, et le développeur peut donc se concentrer sur les
aspects plus globaux.
Les créateurs de Javascript ont repris le concept de tableau associatif pour
créer des objets. On peut donc créer un tableau associatif et l'utiliser ensuite
en se basant sur la syntaxe des objets ou vice-versa.
8.1.1 Attributs
Pour créer un objet, on écrit :
121
122
CHAPITRE 8.
OBJETS
var boite = { largeur: 12, profondeur: 45, couleur: "rouge" }
On peut modier ou ajouter des attributs après la création d'un objet :
boite.hauteur = 20
Cette instruction ajoute l'attribut hauteur et lui aecte la valeur à droite
du signe =. Si cet attribut avait été déni auparavant, la même instruction
aurait simplement changé la valeur de l'attribut.
8.1.2 Méthodes
Les fonctions dénies dans les objets sont appelée méthodes. Avant d'indiquer comment dénir une méthode, considérons les deux façons suivantes
de dénir une fonction.
function maFonction(x) { . . . }
var maFonction = function(x) { . . . }
Ces deux lignes créent exactement la même chose. Jusqu'ici, on a utilisé la
première forme qui est plus proche de notre idée d'une fonction. Cependant
une fonction est liée à une variable de la même manière qu'un objet et le
moteur de Javascript retrouve les caractéristiques de la fonction par le biais
de la variable qui la mémorise.
Pour introduire une méthode dans un objet, on doit utiliser la deuxième
syntaxe :
boite.volume = function() {
return boite.largeur * boite.profondeur * boite.hauteur
}
La fonction peut naturellement être inclue dans la dénition initiale d'un
objet :
boite = {
contenu: ""
volume: function() {
return boite.largeur * boite.profondeur * boite.hauteur
}
}
Evidemment, il n'est pas intéressant d'utiliser le nom boite à l'intérieur
de la fonction, car il faudrait l'adapter chaque fois que le nom de la variable
qui contient l'objet change. C'est pourquoi dans toute méthode d'objet, on
peut utiliser le mot-clé this pour indiquer l'objet dans lequel la méthode est
exécutée :
8.2.
123
COPIES D'OBJETS
boite.volume = function() {
return
.largeur *
.profondeur *
}
this
this
this.hauteur
L'appel de la méthode se fait naturellement au moyen de :
var v = boite.volume()
8.2 Copies d'objets
Comme nous l'avons déjà vu au paragraphe 2.17.5, les copies d'objets
ne copient pas le contenu, seulement le pointeur. Ainsi après l'exécution des
deux premières lignes ci-dessous, l'objet boite et l'objet carton contiennent le
même objet, on peut appeler la méthode sur l'une ou l'autre des variables et
modier les attributs sur l'une ou l'autre : la modication apparaîtra dans
les deux variables. Après la troisième ligne, un nouvel objet a été créé et les
deux variables pointent désormais sur deux objets diérents.
var boite = {arete: 100}
var carton = boite
boite = {arete: 120}
8.3 Création d'objets
Les objets sont surtout utiles quand on doit en créer plusieurs de même
type, par exemple une série de blocs d'information sur des livres. Cette section
présente tout d'abord une première façon de préparer des objets, qui explicite
ce qui se passe, puis une façon plus standard, qui fournit le même service,
mais avec quelques conséquences supplémentaires.
Création simpliée
La fonction suivante crée des descriptions de livres :
function createurDeLivre(_titre, _auteur) {
var celuiCi = {}
celuiCi.titre = _titre
celuiCi.auteur = _auteur
celuiCi.afficher = function() {alert(this.titre)}
return celuiCi
}
var livre = constructeurDeLivre("Le Lotus Bleu", "Hergé")
124
CHAPITRE 8.
OBJETS
Cette dernière ligne n'utilise que les instructions de Javascript décrites
dans les premiers chapitres pour créer l'objet ci-dessous et le retourner à
l'appelant.
{ titre: "Le Lotus Bleu", auteur: "Hergé" }
La troisième ligne de la fonction constructeurDeLivre crée l'attribut titre
dans l'objet et lui aecte la valeur passée en argument.
Dans le code ci-dessus, on a utilisé titre pour l'attribut et le même nom
précédé du signe souligné pour l'argument de la méthode. C'est une convention arbitraire, qu'on utilise souvent pour éviter d'inventer de nouveaux
noms. En fait on aurait pu utiliser exactement le même nom, puisqu'un des
deux est caché dans l'objet par son placement après this.
Pour créer d'autres descriptions de livres, il sut d'appeler la fonction de
création avec les paramètres des autres livres.
Création standard
Les lignes suivantes créent le même objet que précédemment :
function ConstructeurDeLivre(_titre, _auteur) {
this.titre = _titre
this.auteur = _auteur
this.afficher = function() {alert(this.titre)}
}
var livre =
new ConstructeurDeLivre("Le Lotus Bleu", "Hergé")
La dernière ligne crée le livre grâce à l'adjonction de new devant la fonction de création, appelée maintenant constructeur. Par convention, on met
la première lettre d'un constructeur en majuscule, mais cela n'est pas interprété par Javascript.
Le symbole new indique au moteur Javascript qu'il s'agit d'une création
d'objet. Il n'est donc plus nécessaire de créer l'objet vide, {}, comme auparavant, et on peut utiliser this déjà dans le constructeur.
De plus les arguments _titre et _auteur restent accessibles depuis le
code des méthodes l'objet, de même que les variables locales. Par contre
celles-ci ne sont pas accessibles de l'extérieur. livre._titre n'est pas valable.
Les variables crées par this.nom sont dites publiques et les autres (les arguments du constructeur et les variables précédées de var dans son code) sont
dites privées.
Dans l'exemple suivant, on a deux variables distinctes : titre1 et this.titre,
plus une variable globale : quantite. Cet exemple comprend également une
fonction publique, appelable depuis l'extérieur et une fonction locale, qui
n'est appelable que depuis les autres fonctions de l'objet.
8.3.
CRÉATION D'OBJETS
125
function ConstructeurDeLivre(titre1) {
var titre
this.titre = "Nouveau"
quantite = 45
this.afficher = function() {
alert(titre+" "+this.titre)
alert(former(titre1))
}
function former(x) {
return ""+x+""
}
}
Dans la fonction acher, on utilise les deux sortes de variables. Il faut
évidemment prendre garde à ne pas oublier this devant les variables publiques,
sinon on risque de créer ou de modier par mégarde une variable locale.
La fonction (ou méthode, puisqu'elle fait partie d'un objet) acher est
publique et la méthode former est locale, elle ne peut pas être appelée de
l'extérieur.
Tous les objets créés à partir d'un même constructeur forment un ensemble appelé classe. Ainsi dans certains langages, on crée les objets à partir
d'un concept de classe plutôt que de constructeur, mais l'approche est très
semblable.
8.3.1 Utilisation de prototype
Si l'on crée des méthodes dans le constructeur, this.method = function()
{...}, chaque objet immobilisera la place réservée à la référence de la méthode
pour chacune d'elles. Pour éviter cela, il est possible de dénir les méthodes
dans le prototype :
function ConstructeurDeLivre(titre) { . . . }
ConstructeurDeLivre.prototype.method = function() {...}
Le moteur Javascript cherche chaque méthode dans l'objet, puis, s'il ne
la trouve pas, il la cherche dans son prototype. L'utilisation de la méthode
dénie dans le prototype est utilisable exactement de la même façon que si
elle est dénie comme précédemment.
L'utilisation du prototype a quelques subtilités, liées à l'héritage, qui seront vues au paragraphe 8.4.3.
126
CHAPITRE 8.
OBJETS
8.4 Héritage
L'héritage est un concept fondamental en programmation orientée objets.
Ce concept comprend deux aspects, l'héritage d'interfaces (polymorphisme)
et l'héritage d'implémentation.
Le polymorphisme est la possibilité d'appeler une méthode de nom et
d'arguments donnés ou d'accéder à un attribut de nom donné dans des objets
de classes diérentes. L'héritage d'implémentation permet de compléter un
objet par diérentes méthodes ou attributs dénis dans un objet de base
pour créer de nouveaux objets dérivées.
8.4.1 Héritage d'interface
En Javascript, le polymorphisme est automatiquement disponible : on
peut appeler une méthode dans n'importe quel objet, pourvu qu'elle ait le
même nom et le même nombre d'arguments. La syntaxe utilisée pour créer
le polymorphisme, quand il n'est pas prédéni dans le langage, ne peut donc
pas être illustrée à l'aide de Javascript.
Dans l'exemple suivant, deux objets de types diérents possèdent la méthode ache et sont appelés depuis deux cellules du même tableau :
var liste = []
liste[0] = {a: 5, affiche: function() {alert(this.a)} }
liste[1] = {x: 5, affiche: function() {alert(this.x)} }
liste[0].affiche()
liste[1].affiche()
Très brièvement, dans un langage typé, tel Java ou C++, on doit indiquer
de quelle classe sont les objets placés dans le tableau et on ne pourrait pas
placer des objets de deux classes diérentes dans le même tableau. L'héritage
d'interface permet de rendre un aspect des deux objets similaire. Cette façon
de procéder permet de détecter un certain nombre d'erreurs lors de la préparation d'un programme. En Javascript, on a moins à écrire, mais on doit
par contre détecter de telles erreurs, s'il y en a, à l'exécution.
8.4.2 Héritage d'implémentation
Dans le listing suivant, on a déni un constructeur, Produit, qui crée
un objet de base contenant une fonction d'achage. Pour l'exemple, cette
fonction en appelle une autre qui n'est pas dénie dans Produit, mais dénie
plus tard.
function Produit() {
this.affiche = function() {
8.4.
HÉRITAGE
127
alert("Ce produit est une() "+this.nomProduit())
}
}
function Banane (_poids) {
this.poids = _poids
this.nomProduit = function() { // appelée depuis Produit
return "Banane"
}
}
Banane.prototype = new Produit()
function execute() { // création et utilisation de l'objet
var fruit = new Banane(5.0)
fruit.affiche()
}
La fonction Banane dénit les caractéristiques propres au produit banane . La première instruction qui comprend new lie la dénition du constructeur Banane au constructeur Produit par le biais de l'attribut prototype, un
nom réservé de Javascript. Après cette liaison, tous les détails du constructeur introduit dans le prototype (méthodes et attributs) sont accessibles de
l'extérieur comme s'ils étaient directement dénis dans l'objet principal.
Notez bien que la liaison entre le constructeur Banane et le constructeur
Produit est établie par une instruction qui se trouve après la dénition des
deux constructeurs, à l'extérieur de ceux-ci.
Dans l'avant dernière ligne, on crée une banane et on appelle ses méthodes
comme si elle avait été créée au moyen d'un seul constructeur. La fonction
de Produit appelle la fonction de Banane comme si elle était intégrée dans
un unique objet.
Dans cet exemple, on ne peut pas initialiser d'attributs au niveau de
Produit, car Produit() est utilisé dans la dénition, à un moment où on ne
connaît pas encore les valeurs à introduire dans les nouveaux objets.
Note La solution que nous proposons pour résoudre ce problème implique
que le constructeur de l'objet de base et celui de l'objet dérivé ne sont pas
construits tout à fait de la même façon, ce qui nous éloigne un peu des canons
de l'orienté objet. Cependant, cette solution est simple et résout tous les
problèmes. Les solutions qu'on trouve sur la Toile ou dans les livres, si elles
sont plus homogènes, sont complexes, voire obscures et souvent entachées
d'erreurs. Une présentation en est faite sur le site du livre.
Dans l'exemple ci-dessous, on dénit une méthode initProduit qui devra
être appelée depuis le constructeur de Banane. Tous les attributs qui doivent
128
CHAPITRE 8.
OBJETS
être initialisés, voire d'autres, en particulier les tableaux, sont placés dans
cette méthode. Cette méthode possède les arguments qui sont utilisés pour
initialiser les attributs.
function Produit() {
var numero
this.construitProduit = function (_numero, _prix) {
numero = _numero
this.prix = _prix // attribut public
}
this.affiche = function() {
alert(numero+" "+prix+" "+this.autre())
}
}
function Banane (numero, prix, poids) {
this.initProduit(numero, prix)
this.poids = poids // mêmes noms, mais variables distinctes!
this.autre = function () {
return ""+this.poids
}
Banane.prototype = new Produit()
function execute() { // création et utilisation de l'objet
var fruit = new Banane(30012, 4.60, 5.0)
fruit.affiche()
}
Dans le constructeur de Banane, on fait un appel explicite à la méthode
d'initialisation de l'objet Produit, en passant les arguments entrés dans le
constucteur de Banane. Ainsi, on peut entrer les valeurs des attributs de
Produit dans la création de Banane, comme à l'avant-dernière ligne.
8.4.3 Tableau dans un objet hérité
Si l'on dénit un nombre ou une chaîne de caractères dans un prototype,
il est commun à tous les objets jusqu'à ce qu'il soit mis à une nouvelle valeur. Reprenant le constructeur Produit vu précédemment, l'exemple suivant
illustre ce qui se passe avec les attributs dénis dans le prototype :
Produit.prototype.unNombre = 12
var x = new Produit()
x.unNombre = x.unNombre+1
8.5.
FERMETURE
129
Le nombre mémorisé, unNombre, dans le prototype est lu, 1 lui est ajouté
et cette nouvelle valeur est restockée dans l'objet, plus dans le prototype, en
conformité avec ce qui a été vu jusqu'ici.
Par contre, si l'on dénit un tableau dans un prototype, il reste dans le
prototype aussi longtemps qu'il n'est pas réinitialisé :
Produit.prototype.unTableau = []
var y = new Produit()
y.unTableau[2] = 144
y.Tableau = [33, 44]
A la troisième ligne ci-dessus, l'élément 2 du tableau logé dans le prototype est mis à la valeur 144. A la quatrième ligne, un nouveau tableau est
déposé, cette fois-ci dans l'objet y.
Lorsque le moteur Javascript exécute les instructions suivantes,
function Produit() { this.ceTableau = [] }
Banane.prototype = new Produit
le tableau est automatiquement passé dans le prototype, ce qui ne fait pas
forcément notre aaire. Toutefois si l'on crée le tableau dans une fonction
d'initialisation, comme proposé au paragraphe précédent, un tableau est automatiquement créé dans chaque nouvel objet, ce qui élimine le problème
précédent.
Il est parfois intéressant d'avoir un tableau ou un sous-objet unique pour
toute la classe d'objets et dans ce cas, on peut justement utiliser le prototype
pour cet eet.
8.5 Fermeture
Le concept présenté dans cette section est un peu bizarre, mais il est
présent dans plusieurs langages et on l'utilisera au chapitre 10. Il correspond
à un objet simplié qui contient des attributs mais une seule fonction.
La fonction creeFermeture contient une deuxième dénition de fonction.
Lorsque le moteur du navigateur appelle la fonction creeFermeture, un miniobjet contenant la fonction intérieure et les variables locales de la fonction
extérieure (x, y ) sont placés dans la variable globale fermeture.
1
2
3
4
5
var fermeture
function creeFermeture(x){
var y = 10
fermeture = function () { alert(x+y) }
}
130
6
7
CHAPITRE 8.
OBJETS
creeFermeture(5)
fermeture()
L'appel de la ligne 6 crée un mini-objet qui contient la fonction et conserve
un lien sur l'environnement de creeFermeture. Pour appeler la fonction d'une
fermeture, on utilise exactement la même syntaxe que pour appeler une fonction normale (ligne 7).
Dans l'exemple suivant, la fonction est dénie à l'intérieur de setTimeout,
mais la fonction est créée de la façon décrite ci-dessus et garde également
une référence sur l'environnement de creeFermetureTemp.
Pour mémoire, la fonction setTimeout prend comme premier argument
une variable qui contient une (dénition de) fonction, directement une dénition de fonction ou une string contenant une ou plusieurs instructions
Javascript.
1
2
3
4
5
var fermeture
function creeFermetureTemp(x){
var y = 10
setTimeout(function () { alert(x+y) }, 100)
}
Ici, la fonction sera appelée par le manager du temps, ce n'est pas l'utilisateur qui l'appelle. Cette technique permet de passer des arguments à la
fonction. Notez que les variables locales ne passent pas par les arguments
de la fonction à l'intérieur de setTimeout, mais directement dans le corps de
cette fonction.
8.6 Création d'un arbre de tri
Un bel exemple de l'utilisation des objets est la création d'un arbre permettant de faire un tri alphabétique. Cet exemple utilise la récursion, c'està-dire l'appel répété d'une méthode par elle-même.
Comme on le voit dans la gure 8.1, tous les n÷uds à gauche de n'importe
quel n÷ud contiennent des mots placés avant dans l'ordre alphabétique et
ceux qui sont placés à droite contiennent des mots placés après. Les n÷uds de
cet arbre sont des objets qui comprennent les attributs gauche, droite et nom
dont les signications sont explicites. Le programme est disponible parmi les
solutions des exercices.
y2 - y1
perp = - x + x
2
1
A=
8.6.
B=
α
x2
y2
x1
y1
131
CRÉATION D'UN ARBRE DE TRI
gauche
droite
mmm
gauche
droite
gauche
bbb
droite
sss
Figure 8.1 Arbre binaire
Lorsque l'on reçoit le premier mot, on crée un n÷ud, on y introduit le
mot et on l'aecte à la variable qui contiendra le tronc. Puis on prend le
prochain mot, et on le compare avec le mot du premier n÷ud. S'il est plus
petit que ce mot, on crée un n÷ud avec le mot et on le place à gauche. S'il
est plus grand, on place le mot dans un n÷ud à droite.
Pour le troisième mot, les choses se compliquent encore un peu. S'il doit
être placé du côté où il y a déjà un n÷ud, il faut répéter la comparaison avec
le mot du n÷ud suivant. Cependant, si l'on a placé la méthode d'insertion
dans le n÷ud, il sut de dire à ce n÷ud de placer lui-même le nouveau mot.
Si ce n÷ud retrouve à nouveau un n÷ud, il le repasse à ce nouveau n÷ud,
jusqu'à ce qu'un n÷ud ait un pointeur null justement du côté où il faudra
insérer le mot.
Le n÷ud créé par la méthode ci-dessous, contient les deux pointeurs,
l'attribut pour le mot et la méthode qui insère à gauche, à droite ou qui
passe le mot au n÷ud placé du côté où il faudrait insérer le nouveau mot.
function Noeud(nom) {
this.droite = null
this.gauche = null
this.nom = nom
this.insere = function(nom) {
if (nom<this.nom) {
if (this.gauche==null) {
this.gauche = new Noeud(nom)
} else {
this.gauche.insere(nom)
}
} else if (nom>this.nom){
if (this.droite==null) {
this.droite = new Noeud(nom)
} else {
132
CHAPITRE 8.
OBJETS
this.droite.insere(nom)
}
}
}
}
var tronc = null
function execute() {
tronc = new Noeud("mmmm") // insertion du premier n÷ud
tronc.insere("pppp")
tronc.insere("rrrr")
Ainsi le programme ci-dessus passe les mots au n÷ud initial et les autres
n÷uds se passent le mot de branche en branche jusqu'à ce qu'un n÷ud puisse
l'insérer.
Quand cela est fait, une technique similaire peut être utilisée pour imprimer les n÷uds dans l'ordre alphabétique. Chaque n÷ud demande au n÷ud
de gauche d'imprimer son mot et les mots en-dessous de lui, puis il imprime
son propre mot et demande au n÷ud de droite d'acher les mots des n÷uds
sur lesquels il pointe. Evidemment le n÷ud d'en-dessous va faire de même et
le résultat est la liste triée. C'est une méthode de tri ecace.
Voici le code de la méthode à ajouter à l'objet formé par le constructeur
Noeud :
this.parcourir = function() {
if (this.gauche!=null) {
this.gauche.parcourir()
}
texte.push(this.nom) // texte sera affiché à la fin
if (this.droite!=null) {
this.droite.parcourir()
}
}
8.7 Exercices
8.7.1 Objets graphiques
a) Dessin
Contrairement à SVG, Canvas ne mémorise pas les objets qu'il dessine.
Dans cet exercice, on vous propose donc de créer des objets qui contiennent
8.7.
EXERCICES
133
les attributs d'objets graphiques de Canvas et la méthode qui sait dessiner
l'élément décrit par l'objet.
On créera quelques objets qu'on déposera dans un tableau, puis on exécutera une boucle, sur ce tableau, qui appelle la méthode de dessin de chacun
des objets. Cette boucle sera placée parmi les instructions qui forment la
trame de Canvas :
var canvas=document.getElementById("canvas");
ctx = canvas.getContext("2d")
for (var i=0; i<g1.length; i++) {
g1[i].dessine() // boucle de dessin
}
1. Créez un objet Line qui contient les attributs x1, y1, x2 et y2, ainsi
qu'une méthode dessine() qui utilise ces attributs pour dessiner un
segment de droite.
2. Faites de même pour d'autres éléments, cercle ou rectangle.
3. Ajoutez une couleur aux éléments et achez-la.
4. Modiez le code de façon à construire les objets au moyen de constructeurs.
5. Mettez plusieurs objets de chaque forme.
b) Sélection des éléments graphiques *
On propose maintenant de compléter les objets de sorte qu'on puisse
cliquer dans la zone graphique et repérer l'élément près duquel on a cliqué.
La fonction appelée par le clic doit pour cela parcourir le tableau et trouver le
premier élément correspondant. Pour un rectangle ou un cercle, c'est facile, il
sut de tester si les coordonnées du point cliqué est à l'intérieur du rectangle
ou à une distance du centre du cercle inférieure au rayon. Les segments de
droite seront traités plus loin.
Pour repérer la position du curseur par rapport au cadre de Canvas, il
faut introduire l'attribut :
onclick="recherche(event)"
dans la balise canvas. Dans la fonction recherche(), on obtient la coordonnée
horizontale du point cliqué au moyen de event.clientX. De même pour y.
Attention, il faut retrancher la position du cadre par rapport à la fenêtre,
ce qui se fait en utilisant la fonction ndPosition disponible dans le chier
Ex_8.7.1b_Position.js dans la liste des solutions. Cette fonction retourne un
tableau de deux éléments correspondant aux valeurs x et y du coin supérieur
gauche du champ canvas.
134
CHAPITRE 8.
P=
y2 - y1
perp = - x + x
2
1
px
py
B=
α
A=
OBJETS
x2
y2
x1
y1
Figure 8.2 Distance du point P à une droite AB
1. Compléter les objets rectangles
en
leur ajoutant une méthode qui regauche
droite
mmm
tourne true si le point donné
par les coordonnées x et y passées en
argument est dans l'objet (voir la ligne ci-dessous).
2. Dans une fonction appelée par un clic dans la balise de canvas, parcourir
les objets et rechercher le premier qui retourne true et indique donc que
gauche
droite
gauche
droite
le curseur se trouve
dans ou à proximité de l'objet.
bbb
sss
Pour le segment de droite. Il faut déterminer la distance du point du clic
à la droite qui passe par les deux points et vérier si le point se trouve entre
les deux coordonnées en x ou celles en y. Pour calculer la distance, il faut
utiliser le produit scalaire, comme le montre la gure 8.2.
La perpendiculaire au segment AB est donnée par (ABy ;-ABx ), c'est-àdire (y2 − y1 ; −x2 + x1 ). La distance de la droite au point P du clic est
donné par le produit scalaire du vecteur perpendiculaire, ramené à la longueur unitaire, par le vecteur partant de A et aboutissant à P. Elle vaut
donc :
y2 − y1
−x2 + x1
px − x1
py − y1
·
dist = p
(x2 − x1 )2 + (y2 − y1 )2
[(y2 − y1 ) · (px − x1 ) + (−x2 − x1 ) · (py − y1 )]
p
=
(x2 − x1 )2 + (y2 − y1 )2
1. Implémentez la détection pour les segments de droite.
Création interactive des objets
Créez une série de boutons en dehors de la zone canvas et que l'utilisateur cliquera pour indiquer quel élément il désire créer. Mémorisez la position
8.7.
EXERCICES
135
du curseur au moment de l'événement onmousedown, puis, à l'apparition de
l'événement onmouseup, créez l'objet demandé, ajoutez-le au tableau mentionné ci-dessus et redessinez tout le dessin après avoir appelé clearRect.
Plutôt que de changer la couleur d'un élément comme précédemment,
sélectionnez-le, puis, à chaque événement généré par onmousemove ajoutez
à chacune de ses coordonnées le déplacement entre le point du premier clic
et la position courante du curseut et redessinez le dessin complet. L'élément
sélectionné se déplacera.
Vous avez maintenant les éléments de base pour créer un éditeur de Canvas. Il sura de plus de créer des méthodes qui accumulent dans un tableau
les appels aux fonctions de Canvas, plutôt que de les exécuter et d'acher
cette liste dans un élément div. Un utilisateur pourra donc copier-coller ces
instructions pour réaliser le dessin dans une page HTML.
Héritage
Introduisez des attributs correspondant à des transformations et les méthodes que vous jegerez utilss par héritage dans les objets graphiques, et
adaptez les objets pour qu'ils utilisent ces transformations dans la procédure
d'achage.
8.7.2 ex* - Héritage
Dans cet exercice, on propose de reprendre l'exercice de la section 8.6
et de séparer la gestion de l'arbre de la gestion des données dans le but de
pouvoir réutiliser la gestion de l'arbre dans d'autre situation. Comme il est
plus facile de réaliser cela dans un cas précis, on suppose donc qu'on veut
gérer une liste de personnes.
1. Eliminer l'attribut nom dans le Noeud. Cet attribut sera remplacé par
la partie de l'objet qui hérite de Noeud.
2. Créer un constructeur de Personne, qui contient un nom (ou plus si
cela vous convient).
3. Créer une méthode, dans Personne (ou dans son prototype), qui prend
un n÷ud en arguments et qui compare les paramètres de la personne attachée à ce n÷ud avec celui passé en paramètre. Comme cette méthode
compare se trouve forcément dans Personne 1 , les n÷uds contiennent les
attributs correspondant à Personne. Cette méthode retourne -1, 0 ou
1. Si les n÷uds étaient hérités par un autre type d'objet, la méthode compare serait évidemment celle de cet autre objet. Dans tous les cas elle correspond donc automatiquement
au type d'objet dans lequel elle est appelée
136
CHAPITRE 8.
OBJETS
1, suivant que le premier argument est plus grand, égal ou plus petit
que le deuxième.
4. Adapter le constructeur de Noeud en remplaçant partout le nom par un
nouveau n÷ud. Lors de la création, il faudra alors passer des Personne
plutôt que le nom à la méthode insere.
12 août 2010
Chapitre 9
Bases de données
9.1 Introduction
Les bases de données sont nécessaires au fonctionnement d'innombrables
applications sur la Toile. Chaque fois que vous commandez un billet pour
un spectacle ou un voyage, passez en revue des catalogues, recherchez des
informations, vous utilisez des bases de données.
Une base de données est composée d'un ensemble de tables telles que celle
qui est présentée ci-dessous.
+----+------------+----------+----------+---------+-------+
| id | name
| quantity | category | weight | price |
+----+------------+----------+----------+---------+-------+
| 1 | roue
|
76 | aa,c
| 145.000 | 12.45 |
| 2 | pneu
|
88 | b
| 319.600 | 62.60 |
| 3 | pare-brise |
44 |
| 59.100 | 52.20 |
+----+------------+----------+----------+---------+-------+
Une table comprend des lignes et des colonnes. Chaque colonne a un nom
et contient des valeurs du type (entier chaîne de caractères) attribué à la
colonne. Une ligne est aussi appelée enregistrement ou tuple.
Il y a plusieurs variantes du langage SQL, qui dièrent par de petits détails, mais nous nous tiendrons à la variante comprise par la base de données
MySQL, un environnement largement utilisé dans l'industrie et qui existe en
version gratuite. Les instructions que nous présentons ne représentent qu'un
sous-ensemble, mais il sura pour donner une bonne idée des concepts sousjacents et faire des applications intéressantes.
Le langage SQL permet de dénir la forme des tables, comme celle qui est
présentée ci-dessus, de les remplir, de rechercher des informations, de modi137
138
CHAPITRE 9.
BASES DE DONNÉES
er ces informations et nalement de détruire les tables ou des informations
qu'elles contiennent, quand elles sont périmées.
Dans les prochaines sections, nous présentons les commandes de SQL
(Structured Query Langage), puis nous verrons comment les mettre en ÷uvre
pour gérer des aspects plus globaux. Ces commandes peuvent être écrites sur
une ou plusieurs lignes et indentées comme bon vous semble.
9.2 Création d'une table (create)
La commande ci-dessous crée une table vide qui pourra contenir les informations apparaissant dans la table du paragraphe précédent.
create table produits (
id long auto_increment primary key,
nom varchar(20),
quantite int default 0,
categorie set('aa',"b",'c'),
poids double (10,3),
prix decimal (10,2)
);
Les deux premiers symboles ci-dessus, create table décrivent la commande.
Le troisième symbole est le nom de la table choisi par le développeur.
La suite consiste en une liste de types entre accolades et séparés par des
virgules. Le premier symbole de chaque ligne correspond à un nom choisi par
le développeur. Il ne doit évidemment pas comprendre de lettre accentuée,
ni d'espace. Le système ne fait pas de diérence entre une lettre majuscule
et la minuscule correspondante.
Le premier type est particulier. On met en général une colonne de ce type
dans chaque table. Il correspond à un numéro qui identie les lignes. Ces
numéros ne sont pas forcément dans l'ordre, mais chacun d'eux est unique,
c'est ce qui est assuré par les symboles primary key. auto_increment indique
que le numéro est géré par la base de données et que chaque fois qu'on entre
une nouvelle ligne dans la table (voir 9.5) la base de données lui attribue
automatiquement un nouveau numéro, de 1 plus élevé que le précédent.
La quatrième ligne montre comment dénir une valeur par défaut. Le
symbole default est valable pour toutes les lignes et permet de dénir une
valeur qui est utilisée si le programme entre une ligne sans dénir cette colonne.
9.2.
CREATE)
CRÉATION D'UNE TABLE (
139
9.2.1 Types et propriétés des colonnes
Voici les types de données principaux et quelques propriétés qu'on peut
attribuer aux colonnes.
Les lignes montrent les 6 types principaux :
int, long, integer
Représente un entier qui peut contenir des valeurs plus ou moins étendues.
Aujourd'hui, ces types permettent de mémoriser des nombres jusqu'à 263 .
varchar
Ce type correspond à une chaîne de caractères. Le nombre entre parenthèse indique le nombre maximal de caractères qui vont être mémorisés. Si
l'on essaie de déposer une chaîne plus longue, elle sera coupée.
double
Ce type mémorise un nombre réel, possédant donc des chires après la
virgule.
decimal(10,2)
Ce type permet de mémoriser des prix ou des valeurs ayant un nombre
déni de chires après la virgule. Le nombre de caractères après la virgule est
indiqué dans les paramètres. Le nombre ci-dessus possède 10 chires, dont 2
après la virgule. Ce type est utilisé pour représenter des sommes d'argent.
set ('aa', "b", 'c') (ensemble)
Ce type permet de créer un certain nombre de symboles représentant des
caractéristiques ou propriétés attribuées à un objet représenté dans la ligne
d'une table.
Par exemple on pourraît décider que les symboles aa, b et c 1 représentent
trois qualités : jante alu, sans chambre à air et enjoliveurs plastiques. En
lui attribuant ('aa,c'), la roue mémorisée ci-dessus aurait la première et la
dernière de ces qualités. Pour l'insertion dans une base de données, voir
paragraphe 9.5, attention, ne pas mettre d'espace dans la chaîne insérée.
1. les symboles ont été choisis pour que la table ne dépasse pas les bords de la page,
mais dans une application, on choisirait des symboles plus explicites, évidemment.
140
CHAPITRE 9.
BASES DE DONNÉES
time, date, datetime
Les champs time, date et datetime contiennent des dates et/ou des heures
sous forme de chaînes de caractères.
Une durée ou une heure est mise sous la forme "J HH :MM :SS",
"HH :MM :SS", "J HH", "HH :MM :SS", "HH :MM" ou "SS", où chaque
lettre représente un chire. La lettre J représente un nombre de jours.
Une date est représentée par la forme "AAAA-MM-JJ" où les lettres
représentent les années, jours et mois.
La concaténation des deux séparés par un espace, forme un tout mémorisé
dans un champ datetime.
Un certain nombre de fonctions permettent de manipuler des dates, voir
now() à date_add() page 149.
Pour l'insertion dans une base de données, voir paragraphe 9.12.4.
default
Le mot-clé placé sur la quatrième ligne de la commande de création cidessus indique la valeur que l'on veut voir introduite si l'on ne connaît pas
sa valeur au moment de l'introduction d'une nouvelle ligne.
9.3 Achage de la structure d'une table (describe)
La commande describe produits ; permet de visualiser la structure d'une
table, soit pour la vérier, soit pour générer des applications qui découvrent
toutes seules ce qu'il y a dans une table.
La commande ci-dessus, appliquée à la table précédente produit la description suivante, formatée en forme de table.
describe producis;
+-----------+------------------+------+-----+----------------+
| Field
| Type
| Null | Key | Extra
|
+-----------+------------------+------+-----+----------------+
| id
| int(11)
|
| PRI | auto_increment |
| nom
| varchar(20)
| YES |
|
|
| quantite | int(11)
|
|
|
|
| categorie | set('aa','b','c')| YES |
|
|
| poids
| double(10,3)
| YES |
|
|
| prix
| decimal(10,2)
| YES |
|
|
+-----------+------------------+------+-----+----------------+
9.4.
DESTRUCTION D'UNE TABLE (DROP)
141
On peut vérier que la table a une clé auto-incrementée et que la plupart
des colonnes peuvent être indénies (indiqué par la colonne Null).
9.4 Destruction d'une table (drop)
Une table peut être éliminée de la base au moyen de cette commande. On
élimine une table si on veut en construire une avec une autre structure, ce
qui arrive souvent au moment où l'on développe une nouvelle application et
que l'on a mal jugé les éléments dont on a besoin.
drop table produits;
9.5 Insertion de données (insert)
Quand une table a été créée, elle peut recevoir des lignes de données grâce
aux commandes suivantes. La première commande ajoute une ligne complète
dans la table produits. Comme le premier paramètre est une primary key, la
base de données vérie avant l'insertion que la valeur introduite pour cette
clé, dans la première position de values, n'est pas déjà présente dans une
autre ligne.
Dans la deuxième commande on a un 0 dans cette position. Comme la
première colonne contient également auto_increment, la base de données
introduit automatiquement le prochain index disponible.
insert into produits
values(7, 'porte', 93, "aa,cc", 67.200, 73.40);
insert into produits
values(0, 'roue', 76, "aa,cc", 145.0, 12.45)
(0, 'porte', default, "xx", 85.300, 136.20);
Cette deuxième forme montre également comment insérer simultanément
plusieurs lignes dans la base de données. Elle illustre nalement l'emploi de
default. Dans la dernière ligne, dans la colonne correspondant à quantite, on
a placé ce mot-clé. La base de données va donc charger la valeur dénie pour
ces cas dans la commande de création (section 9.2).
Notez que les valeurs numériques sont écrites sans délimiteurs et que les
chaînes de caractères sont encadrées par des guillemets ou des apostrophes.
La prochaine commande indique, après le nom de la table, les colonnes
qui vont apparaître dans la liste de valeurs. Comme l'identicateur n'est pas
inclus dans cette liste, la base de données introduit le prochain index, pour
142
CHAPITRE 9.
BASES DE DONNÉES
les raisons déjà invoquées. Les valeurs non dénies sont laissées vides dans la
base à moins qu'on ait déni des valeurs par défaut à la création.
insert into produits (nom, quantite, categorie, poids, prix)
values ('roue', 76, "", 145.0,12.45);
La commande ci-dessous introduit une troisième forme pour dénire l'insertion d'une ligne. Le paramètre de la clé primaire n'étant pas déni, il est
à nouveau généré automatiquement.
insert into produits
set name='pneu', quantite=88,categorie="bb",
weight=319.600, price=62.60;
9.6 Extraction d'information (select)
Les buts principaux d'une base de données sont l'extraction et le tri des
informations. Ainsi il y a de nombreuses formes de la commande aectée à
ces actions.
9.6.1 Sélection simple
Voici quelques exemples de la commande select suivis des tables qu'ils
peuvent produire.
Entre select et from, on place l'indication des colonnes dont on veut retourner les valeurs.
Dans le premier exemple, on a placé une étoile à cet endroit. Cela signie
que l'on désire voir toutes les colonnes.
select * from table1;
+----+------+------+------+
| id | aaa | bbb | ccc |
+----+------+------+------+
| 1 | x1 | x1 | x1 |
| 2 | x2 | x2 | x2 |
+----+------+------+------+
Dans la commande suivante,
9.6.
EXTRACTION D'INFORMATION (
SELECT)
143
select table2.* from table2;
+----+------+------+------+
| id | aa
| bb
| cc |
+----+------+------+------+
| 1 | y1 | y1 | y1 |
| 2 | y2 | y2 | y1 |
| 3 | y2 | y2 | y2 |
+----+------+------+------+
table2.* signie toutes les colonnes dénies dans table2. Dans la com-
mande ci-dessous, les colonnes à retourner sont identiées.
144
CHAPITRE 9.
BASES DE DONNÉES
select id, aa, bb, cc from table3;
+----+------+------+------+
| id | aa
| bb
| cc |
+----+------+------+------+
| 1 | y1 | y1 | y1 |
| 2 | y2 | y2 | y1 |
+----+------+------+------+
Aussi longtemps que les noms des colonnes ne sont pas ambigus, et donc
que soit il n'y a qu'un nom de table après from, soit les noms de colonnes des
tables dénies après from sont diérents, ils peuvent être inscrits sans nom
de table. Par contre, si l'on veut indiquer des colonnes de même nom dans
deux tables, il faut faire précéder le nom de colonne d'un nom de table et
d'un point (deux noms de table, voir paragraph 9.6.2).
select id, cc, table3.bb from table3;
+----+------+------+
| id | cc
| bb
|
+----+------+------+
| 1 | y1 | y1 |
| 2 | y1 | y2 |
+----+------+------+
9.6.2 Jointures
L'indication from peut être suivie par un nom seul ou par une liste de
noms de tables. Dans ce cas, il faut imaginer que cette liste crée une table 2
contenant le produit carthésien des lignes des tables listées, appelé jointure.
L'exemple ci-dessous illustre le produit des tables table1 et table2 apparaissant ci-dessus. La table résultante contiendra toutes les colonnes de table1
suivies des colonnes de table2. Chaque ligne de la pemière table apparaît en
combinaison avec chacune des lignes de la deuxième table. Comme ces tables
ont respectivement 2 et 3 lignes, la table résultante aura donc 6 lignes, ce
qu'on peut suivre en examinant les id des deux tables.
2. La plupart du temps, on extrait des éléments de cette table et le système ne la
construit pas en entier, mais crée directement l'extraction
9.6.
EXTRACTION D'INFORMATION (
SELECT)
145
select * from table1, table2;
+----+------+------+------+----+------+------+------+
| id | aaa | bbb | ccc | id | aa | bb | cc |
+----+------+------+------+----+------+------+------+
| 1 | x1 | x1 | x1 | 1 | y1 | y1 | y1
|
| 2 | x2 | x2 | x2 | 1 | y1 | y1 | y1
|
| 1 | x1 | x1 | x1 | 2 | y2 | y2 | y1
|
| 2 | x2 | x2 | x2 | 2 | y2 | y2 | y1
|
| 1 | x1 | x1 | x1 | 3 | y2 | y2 | y2
|
| 2 | x2 | x2 | x2 | 3 | y2 | y2 | y2
|
+----+------+------+------+----+------+------+------+
Si l'on ne dénit pas toutes les colonnes dans le select, il peut arriver que
des colonnes qui font la diérence entre deux lignes soient éliminées, auquel
cas des lignes risquent d'apparaître en doubles ou multiples exemplaires,
comme la table ci-dessous qui est extraite de la table précédente..
select aa,bb,bbb,ccc from table1, table2 ;
+------+------+------+------+
| aa
| bb
| bbb | ccc |
+------+------+------+------+
| y1
| y1
| x1
| x1 |
| y1
| y1
| x2
| x2 |
| y2
| y2
| x1
| x1 |
| y2
| y2
| x2
| x2 |
| y2
| y2
| x1
| x1 |
| y2
| y2
| x2
| x2 |
+------+------+------+------+
Les doublons peuvent être éliminés en utilisant le symbole DISTINCT,
comme montré ci-dessous.
select distinct aa,bb,bbb,ccc from table1,table2 ;
+------+------+------+------+
| aa
| bb
| bbb | ccc |
+------+------+------+------+
| y1
| y1
| x1
| x1 |
| y1
| y1
| x2
| x2 |
| y2
| y2
| x1
| x1 |
| y2
| y2
| x2
| x2 |
+------+------+------+------+
146
CHAPITRE 9.
BASES DE DONNÉES
9.6.3 Critères de sélection (where)
La clause where dénit les lignes qui sont sélectionnées dans la ou les tables
accédées. Cette clause contient une expression Booléenne qui peut être vraie
ou fausse pour chaque ligne. Seules les lignes pour lesquelles l'expression est
vraie sont retournées par la commande. Par exemple :
select * from table2 where id>1 and id<3;
+----+------+------+------+
| id | aa
| bb
| cc |
+----+------+------+------+
| 2 | y2 | y2 | y1 |
+----+------+------+------+
select table2.*,table3.* from table2, table3
where table2.aa=table3.aa and table2.bb=table3.bb
and table2.cc=table3.cc;
+----+------+------+------+----+------+------+------+
| id | aa
| bb
| cc | id | aa | bb | cc |
+----+------+------+------+----+------+------+------+
| 1 | y1 | y1 | y1 | 1 | y1 | y1 | y1
|
| 2 | y2 | y2 | y1 | 2 | y2 | y2 | y1
|
+----+------+------+------+----+------+------+------+
Dans la requête ci-dessous, les lignes qui sont prises en compte sont celles
dont la valeur de la colonne est contenue dans la liste retournée par le select
emboîté.
select * from table2 where id in (1, 2)
+----+------+------+------+
| id | aa
| bb
| cc |
+----+------+------+------+
| 1 | y1 | y1 | y1 |
| 2 | y2 | y2 | y1 |
+----+------+------+------+
L'ensemble placé à côté de IN peut également contenir des chaînes de
caractères. Cet ensemble peut être construit par un select qui retourne une
liste de valeurs. On appelle un tel select emboîté.
9.6.
EXTRACTION D'INFORMATION (
SELECT)
147
select * from table1 where id in (select id from table1)
+----+------+------+------+
| id | aa
| bb
| cc |
+----+------+------+------+
| 1 | y1 | y1 | y1 |
| 2 | y2 | y2 | y1 |
+----+------+------+------+
9.6.4 Fonction utilisables dans where
Le partie where d'une commande select peut utiliser les fonctions et opérateurs listés ci-dessous. On peut utiliser les parenthèses pour composer des
expressions.
=
and
>
or
<
<> (or !=)
not
is null
<=
>=
is not null
Pour illustrer de façon simple les résultats de certaines comparaisons,
elles sont introduites dans des sélections sans tables. Il est en eet possible
d'envoyer ces lignes à la base de données, pour vérier des résultats, mais elles
auraient évidemment le même eet si elles étaient placées entre des noms de
colonnes qui retournent les valeurs comparées. Le résultat d'une expression
Booléenne est soit 0 (false), soit 1 (true), soit null.
select 3<7,234+22; // produit une valeur Booléenne et un nombre
select 0=NULL; // le résultat est NULL, pas false. Quand un des membres
risque d'être NULL il faut utiliser le comparateur suivant
select 1<=>NULL; // le résultat est 0 (false). <=> a sinon la même signication que =
select 4="4aa"; // une chaîne de caractères est transformée en un nombre
(jusqu'à la première lettre, comme en Javascript))
select 0="aa"; // Si la chaîne ne commence par aucun chire, le système
la prend pour la valeur 0
select CURDATE()< DATE_ADD(CURDATE(), INTERVAL 1 DAY); // comparaison
de dates
9.6.5 Quelques-unes des fonctions dénies par MySQL
MySQL dénit une longue liste de fonctions qui peuvent être utilisées
après les select pour certaines d'entre elles et dans le corps des commandes
pour d'autres, en concordance avec les fonctions. Voici les plus utilisées.
148
CHAPITRE 9.
BASES DE DONNÉES
SUM (expr), AVG (expr), MIN (expr), MAX (expr)
Ces fonctions retournent la somme, la moyenne, le minimum et le maximum calculé sur toutes les lignes des colonnes désignées dans les expressions.
Un exemple est donné ci-dessous.
select avg(weight), sum(weight), min(weight) from table;
Il est possible d'utiliser une clause group by (voir paragraphe 9.6.7) pour
créer une liste de moyennes, par exemple pour chacun des étudiants.
count (*), count (expr), count (distinct expr1 . . .)
La première forme retourne le nombre de lignes sélectionnées par les paramètres qui suivent select.
select count(*) from table1;
La seconde forme retourne le nombre de fois que l'expression est non nulle.
select count(table1.bb) from table1;
La dernière forme retourne le nombre de lignes distinctes dont aucun des
champs placés dans les arguments n'est nul.
select count(distinct table1.bb, table1.cc) from table1;
LAST_insert_ID( )
Cette fonction retourne la dernière valeur créé par la clause auto_increment. Cela permet d'introduire cette valeur dans une autre ligne, par exemple
pour créer une relation (paragraphe 9.9)
insert
insert
insert
insert
into
into
into
into
products values( null, 'roue', 44, "", 59.1, 52.20);
reservations SET id={last_insert_id()};
products values( 0, 'door', 12, "xx", 85.3, 136.20);
reservations values ({ last_insert_id()}, default);
SUBSTRING(str, pos, len)
Cette fonction retourne la sous-chaîne partant de pos et de longueur len.
Attention, le premier caractère vaut 1.
9.6.
EXTRACTION D'INFORMATION (
SELECT)
149
strExpression like `xxx%yyy_zzz'
L'opérateur like retourne true si deux chaînes sont équivalentes. Le signe
% à droite de LIKE représente n'importe quelle séquence de caractères. Le
signe _ représente un seul caractère. Si un de ces caractères ou un \ doit être
déni, ils doivent être précédés par un \ : \%, \_ ou \\.
now()
Cette fonction retourne la date et l'heure dans une chaîne, telle que :
2003-06-1917:31:19
curdate()
Cette retourne la date courante.
curtime()
Cette fonction retourne l'heure courante.
date_add(date, interval 3 minute)
Cette fonction retourne une date augmentée de 3 minutes. Par exemple :
now()
date\_add(now(),interval 3 minute)
// 2003-06-19 17:35:32
// 2003-06-19 17:38:32
D'autres exemples de valeurs possibles sont données ci-dessous :
12 second
30 minute
2 hour
5 day
2 month
10 year
"1:30" minute\_second
"1:45" hour\_minute
"2:02:10" hour\_second
"1 12" day\_hour
"1 11:30" day\_minute
"1 11:30:10" day\_second
"1-6" year\_month
150
CHAPITRE 9.
BASES DE DONNÉES
date_sub(date, interval 3 minute)
Comme ci-dessus, mais pour la soustraction.
9.6.6 order by grouping_columns
Si l'on ajoute cette commande après un select, la base de données trie
les lignes selon les valeurs déposées dans les colonnes indiquées. On donne
ci-dessous quelques exemples. Les nombres correspondent aux colonnes spéciées à la suite du select. La première colonne a le numéro 1. Une colonne
suivie de desc est triée par ordre décroissant.
select table1.ccc, table2.cc
from table1, table2
order by 1 desc, 2 desc;
9.6.7 group by
Dans le cas simple, group by a le même eet qu'order. Dans le cas où un
select inclut des fonctions telles que AVG, count ou SUM, et fait référence
à plusieurs tables, group BY est obligatoire, et ces fonctions calculent alors
plusieurs valeurs, c'est-à-dire une pour chaque groupe indiqué après group
BY.
select table1.ccc, table2.cc
from table1, table2
group by table1.ccc, table2.cc desc;
9.7 Mise à jour des lignes d'une table
Les lignes peuvent être mises à jour au moyen des commandes suivantes.
update products
set weight=33.33
where id=2;
La commande suivante ajoute 2 au poids et ensuite 33.33, c'est-à-dire
35.33 au total.
update products
set weight=weight+2, weight=weight+33.33
where id=2;
9.8.
151
ELIMINATION D'UNE LIGNE D'UNE TABLE
9.8 Elimination d'une ligne d'une table
Les lignes peuvent être éliminées d'une table par la commande suivante.
delete from products
where products.weight<80;
La commande précédente eace les lignes dans lesquelles weight vaut
moins que 80. La commande retourne le nombre de lignes éliminées. S'il
n'y a pas de clause where toutes les lignes sont éliminées.
delete from products,reservations
where products.id=1 and reservations.id=products.id;
Une commande qui a plusieurs noms de table après le symbole from élimine des lignes dans plusieurs tables. Rappelez-vous que from products,
reservations représente le produit carthésien des lignes des deux tables,
c'est-à-dire une table dans laquelle les lignes sont faites d'une ligne de la
première table et d'une ligne de la seconde table.
9.9 Relations entre tables
Dans la plupart des applications des bases de données, une partie des
colonnes des tables font références à des lignes d'autres tables. On a, par
exemple, une table de clients, une table de factures une table de produits,
etc. Chaque facture est liée à un client et contient une liste de références à
des produits. Les connexions entre tables peuvent être très complexes, mais
nous resterons dans des cas relativement élémentaires.
La première table ci-dessous contient une liste de vins avec quelques caractéristiques et un numéro indiquant le marchand qui le livre. On pourrait
mettre directement le nom du marchand dans la table des vins, mais le marchand a d'autres caractéristiques : adresse, liste de livraisons en cours, etc.
et on ne peut pas mettre toutes ces caractristiques dans la table des vins. De
plus elles seront vraisemblablement utilisées dans d'autres tables.
Dans les tables ci-dessous, on sait par exemple que Jean livre du Bourgogne en lisant la colonne marchand de la table des vins. Le numéro mémorisé
dans la ligne Bourgogne est celui qui est attribué à Jean dans la colonne idm
de la table des marchands.
idv
1
2
3
region
Vins
cepage annee marchand
Lavaux
chasselas
Chianti
sangiovese
Bourgogne pinot noir
2005
2002
2000
3
3
1
Marchands
idm nom
1
3
Jean
Luc
Table marchands
refV
refM
relationVM
Table vins
152
CHAPITRE 9.
Table vins
0..n
Å livre
0..n
est livré par Æ
BASES DE DONNÉES
Table marchands
Figure 9.1 Relations entre vins et les marchands
Table produits
La relation entre ces tables est schématisée sur la gure 9.1. Notez que
dans une application réelle d'un peu d'importance, on a facilement une dizaine à plusieurs dizaines de tables.
no_p
En examinant les tables des vins
et des marchands,
onTable
pourrait
savoir qui
fournisseurs
no_u
no_f
Table usines
livre le chasselas en utilisant la première
instruction ci-dessous qui retourne le
livraisons
numéro du marchand et en exécutant la deuxième instruction avec le numéro
obtenu dans la première.
select marchand from vins where cepage="chasselas"
On obtient NUMERO. Le nom du marchand est donc obtenu par :
select nom from marchands where idm=NUMERO
Cependant il est possible de ne faire qu'une seule requête, ce qui est fait
normalement. Une première possibilité est donnée ci-dessous.
select nom from vins,marchands
where marchand=idm and cepage="chasselas"
Cette requête crée virtuellement le produit cartésien qui contient les lignes
suivantes :
idv
1
2
3
1
2
3
region
Lavaux
Chianti
Bourgogne
Lavaux
Chianti
Bourgogne
cepage
chasselas
sangiovese
pinot noir
chasselas
sangiovese
pinot noir
annee marchand idm nom
2005
2002
2000
2005
2002
2000
3
3
1
3
3
1
1
1
1
3
3
3
Jean
Jean
Jean
Luc
Luc
Luc
Parmi ces lignes, les lignes dans lesquelles les numéros des colonnes marchand et idm ne sont pas identiques ne représentent rien, alors que les autres
contiennent des informations cohérentes. Ces lignes sont retenues par la première partie du where ci-dessus (marchand=idm). Lorsque la requête précise
ensuite cepage="chasselas", on obtient alors la ligne recherchée et l'on peut
extraire la colonne qui nous intéresse, nom placé entre select et from.
Une autre façon de faire se base sur le select emboîté (9.6.3).
9.10.
153
RELATIONS N X M
refV
refM
relationVM
Table vins
Table marchands
Figure 9.2 Relation entre les marchands et les vins
Table vins
Å livre
0..n
est livré par Æ
marchands
0..n
Table marchands
select nom from
where idm in (select marchand from vins
where cepage="chasselas")
Table produits
Le select emboîté retourne la liste des numéros de marchands qui livrent
du chasselas. Le select principal retourne les noms pour les lignes dont la
colonne idm est contenue dans cette
liste.
no_p
Table usines
no_u
no_f
livraisons
Table fournisseurs
9.10 Relations n × m
Dans la section pécédente, on ne peut pas exprimer le fait qu'un marchand
peut fournir plusieurs vins et que chaque vin puisse être fourni par plusieurs
marchands. Pour cela, il faut construire une table supplémentaire qui établit
une telle relation entre deux autres tables.
idv
1
2
3
region
Vins
cepage
Lavaux
chasselas
Chianti
sangiovese
Bourgogne pinot noir
annee
2005
2002
2000
Marchands
idm nom
1
3
Jean
Luc
relationVM
refV refM
2
3
3
1
1
1
3
3
La table ci-dessus nous indique que Jean (1) livre du Chianti (2) et du
Bourgogne (3). Luc (3) livre du Lavaux (1) et du Bourgogne (3).
Cette relation s'exprime au moyen du schéma de la gure 9.2.
refV
refM
relationVM
Table vins
154
0..n
Table vins
Table marchands
Å livre
0..n
Table marchands
est livré par Æ CHAPITRE 9. BASES
DE DONNÉES
Table produits
Table usines
no_p
no_u
no_f
livraisons
Table fournisseurs
Figure 9.3 Relations entre les 4 tables
9.11 Exercices
9.11.1 ex - Questions sur une structure existante
Le chier http://siteLivre/exoBD.txt contient une liste de commandes qui
créent les quatre tables ci-dessous, reliées entre elles selon la gure 9.3, et
qui y insère des données. Copiez le contenu de ce chier dans la fenêtre du
moniteur de SQL et exécutez-les.
Usines No_u, Nom_u, Ville
Produits No_p, Nom_p, Couleur, Poids
Fournisseurs No_f, Nom_f, Statut Ville
Livraisons No_p, No_u, No_f, Quantité
Déterminez ensuite les commandes qui répondent aux questions posées
ci-dessous.
1. Trouver le numéro, le nom et la ville de toutes les usines.
2. Trouver le numéro, le nom et la ville de toutes les usines de Lausanne.
3. Trouver les numéros des fournisseurs qui approvisionnent l'usine n◦ 1 en
produit n◦ 1.
4. Trouver le nom et la couleur des produits livrés par le fournisseur n◦ 1.
Ecrire les requêtes SQL permettant de répondre aux questions ci-dessous
5. Trouver les numéros des fournisseurs qui approvisionnent l'usine n◦ 1 en
un produit rouge.
6. Trouver les noms des fournisseurs qui approvisionnent soit une usine
de Lausanne soit une de Genève en un produit rouge.
7. Trouver les numéros des produits livrés à une usine par un fournisseur
de la même ville que l'usine.
9.12.
ACCÈS À LA BASE DE DONNÉES PAR JAVASCRIPT
155
8. Trouver les numéros des produits livrés à une usine de Lausanne par
un fournisseur de Lausanne.
9. Supprimer tous les produits de couleur rouge.
10. Changer la ville du fournisseur n◦ 1 : il a déménagé à Genève.
11. Trouver les numéros des usines qui ont au moins un fournisseur qui
n'est pas de la même ville que l'usine.
12. Trouver les numéros des fournisseurs qui approvisionnent à la fois les
usines n◦ 1 et n◦ 2.
13. Trouver les numéros des usines qui utilisent au moins un produit disponible chez le fournisseur n◦ 3 (c'est-à-dire un produit qu'il est capable
de livrer, mais que l'usine n'obtient pas forcément de ce fournisseur).
14. Trouver le numéro du produit le plus léger (les numéros, si plusieurs
produits ont ce même poids).
15. Trouver les numéros des usines qui ne reçoivent aucun produit rouge
d'un fournisseur de Lausanne.
16. Trouver les numéros des fournisseurs qui fournissent au moins un produit fourni par au moins un fournisseur qui fournit au moins un produit
rouge.
17. Trouver tous les triplets (VilleF, No_P, VilleU) tels qu'un fournisseur
de la première ville approvisionne une usine de la deuxième ville avec
un produit NP.
18. Même question qu'en 15, mais sans les triplets où les deux villes sont
identiques.
9.12 Accès à la base de données par Javascript
Les applications qui gérent des données placées dans une base, accèdent à
cette base au moyen de SQL. Or les réponses aux requêtes SQL sont adaptées
à la vision d'un humain, pas d'un programme. Pour aborder ce problème, on
utilise des librairies et des astuces présentés dans cette section.
Le système LemanOS permet à chaque utilisateur (pour lequel on a déni
un compte) d'accéder à une base de données et de faire des requêtes SQL et
de traiter les réponses de façon simple.
9.12.1 Librairies
Les librairies d'interface au système sont disponibles dans les chiers suivants, qu'il faut donc les importer dans les applications qui doivent gérer des
bases de données sur LemanOS.
156
CHAPITRE 9.
BASES DE DONNÉES
<script src='/LemanOS/dwr/engine.js'>
</script>
<script src='/LemanOS/database.js'>
</script>
9.12.2 Requêtes SQL en Javascript
Les requêtes SQL sont simplement placées dans des chaînes de caractères
et envoyées au moyen de l'instruction suivante :
var result = database.query("select * from vins")
Au retour des requêtes select, le résultat contient un tableau d'objets.
Pour les autres requêtes, le système retourne le nombre de lignes modiées ou
-1 s'il y a une erreur, comme le moniteur. Par exemple la requête précédente
eectuée sur la table ci-dessous :
idv
1
2
3
Vins
region
cepage
Lavaux
Bourgogne
chasselas
sangiovese
annee
2005
2002
retournerait les résultats qui suivent :
[
{'idv':1,'cepage':'chasselas','region':'Lavaux','annee':2005},
{'idv':2,'cepage':'sangiovese','annee':2002},
{'idv':3, 'region':'Bourgogne'}
]
Les champs nuls n'apparaissent pas dans les objets.
Supposant que le résultat soit déposé dans result comme indiqué dans la
requête, on obtient les diérents champs au moyen de
result[0].idv result[0].cepage result[0].region result[0].annee
result[1].idv result[1].cepage result[1].annee
Pour déterminer si un champ n'a pas été déni, il sut de faire le test :
if (!result[1].region) {
alert("Pas défini")
}
9.12.
ACCÈS À LA BASE DE DONNÉES PAR JAVASCRIPT
157
Le nombre de lignes retournées est évidemment obtenu par result.length.
Attention, même si le système ne retourne qu'une ligne, elle est placée dans
un tableau.
Pour détecter d'éventuelles erreurs d'exécution, on utilise la séquence
d'instructions ci-dessous :
var result
try {
result = database.query("select * from vins")
} catch(e) {
alert("Erreur: "+e+" <<"+database.readyQuery+">>")
return
}
// continue ici, en cas de succès
if (result.length==0) {
// pas d'erreur mais table vide
}
L'attribut database.readyQuery placé dans l'alerte contient la chaîne SQL
envoyée à la base de données après remplacement des ? . Cela aide certaines
fois à comprendre les erreurs.
9.12.3 Introduction de variables dans la requête SQL
Lorsque l'on veut introduire des données entrées par un utilisateur dans la
base de données, il faut bien adapter la commande SQL pour qu'elle contienne
les données actuelles. De même pour les sélections. Il y a plusieurs possibilités
de faire cela, ce qui permet d'entrer des données selon les diérentes formes
de SQL. Ces diérentes formes peuvent être combinées.
Concaténation
Dans la forme la plus simple la chaîne est formée par concaténation :
var laRegion = "Lavaux"
var result = database.query("select * from vins "
+"where region='"+laRegion+"'")
Attention, il faut introduire soi-même les apostrophes qui encadreront
Lavaux après la synthèse de la chaîne, comme sur le moniteur.
158
CHAPITRE 9.
BASES DE DONNÉES
Passage de paramètres
Pour simplier les instructions, on a introduit la possibilité de placer un
ou plusieurs points d'interrogations, qui seront remplacés par le système par
les paramètres qui suivent la chaîne SQL. Le système détecte lui-même s'il
s'agit de chaînes ou de nombres et ajoute lui-même les apostrophes.
var laRegion = "Lavaux"
var result = database.query("select * from vins "
+"where region=?", laRegion)
Tableau de valeurs
Dans la première forme de l'insert, il faut introduire une liste de valeurs.
Cela correspond exactement à un tableau, dans lequel l'ordre est respecté.
Dans l'exemple ci-dessous, le ? est remplacé par le tableau en deuxième paramètre.
var data = [0, 'Barolo', 'nebbiolo', null]
var result = database.query(
'insert into vins values (?)' ,
data )
data étant un tableau, le ? est remplacé par les valeurs de data séparées par des virgules. Les chaînes sont automatiquement encadrées par des
apostrophes.
Comme cette requête est une action, result reçoit le nombre de lignes
modiées : 1
Objet en paramètre
De même qu'on peut recevoir un objet, on peut transmettre un objet. Ce
dernier s'intègre parfaitement dans la deuxième forme d'insertion.
var vin = {'region':'Bordeaux', 'cepage':'merlot'}
var result = database.query(
'insert into vins set ?' , vin )
vin étant un objet, le ? est remplacé par les couples nom-valeur de la
variable vin séparés par des virgules. De nouveau, les chaînes sont encadrées
par des apostrophes.
9.12.
ACCÈS À LA BASE DE DONNÉES PAR JAVASCRIPT
159
9.12.4 Insertion d'une date
Le code ci-dessous crée la date courante et la stocke dans un champ
datetime.
date = new Date()
dateSQL = date.getFullYear() + "-" + (date.getMonth()+1)
+ "-" + date.getDate() + " " + date.getHours()
+ ":" + date.getMinutes() + ":" + date.getSeconds()
. . .
database.query("insert into unite set jourHeure=?",dateSQL)
Dans le code ci-dessous, on a complété l'objet Date de Javascript pour
faciliter la construction des dates en format SQL.
Date.prototype.sql = function () {
return this.getFullYear() + "-" + (this.getMonth()+1)
+ "-" + this.getDate() + " " + this.getHours()
+ ":" + this.getMinutes() + ":" + this.getSeconds()
}
. . .
database.query("insert into timeTable set jourHeure=?",
new Date().sql())
9.12.5 Obtention de la clé primaire
Lorsque la clé primaire est générée automatiquement, et qu'on veut l'introduire dans une autre table pour créer une relation, on peut l'obtenir en
appelant
idNb = database.last_insert_id()
juste après la requête qui a provoqué la création automatique d'une nouvelle
clé primaire.
9.12.6 Lecture de toutes les colonnes
Il est possible d'acher toutes les colonnes d'une réponse en les parcourant automatiquement. Notez que les champs sont dans n'importe quel ordre
et que cette façon de faire n'est utile que pour faire des essais. Dans une
application réelle, on ache les champs de façon spécique.
160
CHAPITRE 9.
BASES DE DONNÉES
Couche métier
Couche affichage
handleVins() {
lit les champs Æ objet
database.query("insert into Vins set ?", objet)
}
une ligne
Couche BD
tables de l’application
exec() {
var x = database.query("select * from Vins")
displayVinsM(x)
}
n lignes
Figure 9.4 Architecture 3 tiers
var result = [ {'a':2, 'b':3} ]
for ( key in result [0] ) {
document.write( result [0] [key] )
}
document.close()
9.13 Exercice
9.13.1 ex - Marchand de vins
On doit créer une application qui pourra gérer des vins, les marchands
qui les livrent et la quantité de bouteilles de chaque sorte de vin livrée par
chaque marchand.
Toute application de ce type comprend trois couches (voir gure 9.4) :
une couche d'achage, une couche de gestion des données et une couche
intermédiaire dit couche métier, qui va chercher les données dans la base de
données et les ache, ou lit ce que l'utilisateur a entré dans les tables HTML
d'achage et les insère dans la base de données.
La couche d'achage est contituée de pages HTML. Chaque page permet
de faire une ensemble cohérent d'opérations (gestion des marchands, des vins
gestion du stock, etc) en présentant deux sortes de tables HTML (gure 9.5 :
des tables qui ache les données d'une ligne d'une table SQL, les champs
étant placés les uns sous les autres et des tables HTML dont les lignes et les
colonnes reètent celles des tables SQL.
Des liens permettent de passer d'une page d'accueil aux diérentes pages
9.13.
161
EXERCICE
Figure 9.5 Tables HTML
d'opération, de revenir sur la page d'accueil, voire de sauter d'une page à
l'autre.
La couche de la base de données est accédée principalement au moyen
de SQL. Le moniteur SQL (menus d'outils) est utile dans cette couche pour
initialiser les tables et pour tester les commandes SQL avant de les intégrer
dans la couche métier.
Les diérentes opérations de la couche métier sont déclenchées au chargement des pages, lorsque l'utilisateur clique un bouton ou l'autre, ou lors
qu'il sélectionne les lignes des tables. Cette couche contient donc la logique
qui gère l'application.
Créez dans a base de données une table contenant des vins avec quelques
caractéristiques, une table de marchands avec leur nom ainsi qu'une
table dont chaque ligne représente un vin en stock, dont on connaît les
caractéristiques (référence dans la table des vins), le marchand qui l'a
livré et le nombre de bouteilles.
Créez une page HTML qui permet d'entrer des vins et des marchands.
Le système LemanOS permet de générer les tables HTML et des fonctions générales achant des lignes ou des groupes de lignes. Voir les
détails dans le générateur d'interfaces parmi les outils de ce système.
Créez une page qui permet d'acher tous les vins dans une première
table et tous les marchands dans une deuxième table. Ces tables peuvent
être chargées par l'attribut onload dans body, en faisant une requête
SQL sur les tables et utilisant les méthodes d'achages produites par
162
CHAPITRE 9.
BASES DE DONNÉES
le générateur.
Quand on clique sur une ligne d'une table à plusieurs lignes, une fonction est appelée. Cette fonction dépose le numéro de la ligne dans
une variable globale. Créer un bouton qui permet d'entrer un nombre
de bouteilles (champ d'input ) dans le stock en utilisant les lignes sélectionnées dans la table de vins et celle de marchands. Si le programme a gardé les tableaux de lignes qui ont servi à charger les tables
au point précédent, on peut lire les identicateurs dans ces tables :
vins[ligneSelectionnee].idv.
Créez un deuxième bouton qui permet de sortir du stock un nombre de
bouteilles correspondant au nombre dans le champ d'input.
Modiez la page précédente pour qu'on puisse sélectionner un vin et
ne voir dans la deuxième table que les marchands qui livrent ce vin.
Créez une page d'accueil qui permet d'appeler l'une des deux pages cidessus au moyen d'un lien indiquant les opérations disponibles. Mettez
des liens sur les deux pages pour revenir sur la page d'accueil.
9.13.2 ex - Application de gestion de fournisseurs
Dans cet exercice, on va gérer une base de données contenant des descriptions de fournisseurs, de produits et de commandes. Une table auxiliaire sera
utilisée pour dénir les quantités de produits en stock ou dans les commandes.
Une marche à suivre est disponible dans les résultats à obtenir liés aux
exercices de ce manuel.
9.13.3 ex- Organisation d'un musée
Dénir un diagramme entité-association représentant les faits suivants,
relatifs à un musée :
toute oeuvre du musée a un titre, un ou plusieurs auteurs, une date
d'acquisition et un numéro de catalogue (identiant) ;
une oeuvre est exposée dans l'une des salles du musée (qui est caractérisée par un numéro, son nom, le nombre d'oeuvres exposables, sol,
éclairage), ou est en prêt dans un autre musée (nom et adresse de ce
musée, début et durée du prêt) ;
certaines oeuvres exposées dans le musée peuvent avoir été empruntées
par le musée, soit à un autre musée, soit à un particulier (nom et
adresse). Dans ce cas, on connaît son titre, son (ou ses) auteur(s), la
date de début et la durée de l'emprunt. De plus, l'oeuvre doit alors être
assurée. On veut savoir le montant de la prime d'assurance, la valeur
9.13.
EXERCICE
163
pour laquelle l'oeuvre est assurée, le nom et l'adresse de la compagnie
qui l'assure ;
le conservateur garde le chier des musées et des particuliers qui ont
prêté ou qui sont susceptibles de prêter des oeuvres. Pour chacun (musée ou particulier), il garde le nom et l'adresse et la liste des collections
qui l'intéressent (art déco, art contemporain, antiquités, ...).
164
CHAPITRE 9.
BASES DE DONNÉES
12 août 2010
Chapitre 10
Applications pseudo-parallèles
10.1 Tâches pseudo-parallèles
Votre ordinateur exécute beaucoup de tâches : il gère les messages du
réseau, répond aux clics de la souris, aux touches du clavier, teste les chiers
qui entrent et vous avez certainement vu qu'il utilise le disque à tout moment
sans que vous le demandiez. Comme il n'a qu'un (peut-être deux) processeurs,
il doit interrompre une tâche dès qu'il y en a une plus urgente, puis reprendre
une tâche interrompue exactement là où elle en était restée. Il faut donc qu'il
y ait une organisation des programmes qui permette de suivre chaque tâche et
de savoir où elle en est. Certaines tâches en enclenchent d'autres et attendent
des résultats d'autres encore.
Un système qui permet de gérer un tel ensemble de tâches est appelé
multi-tâche, multi-thread (voir plus loin), pseudo-parallèle, concurrent, ou
même parallèle ou de temps-réel, car il donne l'illusion qu'il est toujours prêt
à répondre à l'utilisateur, quelle que soit la tâche qu'il est en train d'eectuer.
On entend souvent dire qu'une telle organisation permet d'eectuer un
travail plus rapidement, mais s'il n'y a qu'un processeur, ce ne peut évidemment pas être le cas. Toutefois, les problèmes de gestion de la mémoire
qui surgissent dans le cas d'un seul processeur sont les mêmes que ceux qui
apparaissent dans une système multi-processeur.
Les langages courants tels Java, C ou C++ orent des instructions ou
des librairies pour créer des programmes pseudo-parallèles, ce qui n'est pas
le cas de Javascript. Ce dernier ne peut même pas suspendre l'exécution d'une
fonction pour quelques millisecondes et la continuer après ce laps de temps.
Pour faire quelque chose approchant ce comportement, il faut décomposer la
fonction en deux morceaux et utiliser l'instruction setTimeout. Cependant,
grâce à cette instruction, on peut donner l'illusion que le système est toujours
prêt à répondre, même s'il est en train de faire bouger des éléments sur la
165
166
CHAPITRE 10.
APPLICATIONS PSEUDO-PARALLÈLES
page, ainsi que nous l'avons déjà vu dans l'exercice 3.6.3.
Dans ce chapitre, nous allons voir comment organiser des activités pseudoparallèles, tout d'abord au moyen de setTimeout, puis au moyen d'un compilateur qui insère lui-même les setTimeout et ore ainsi des possibilités proche
de celles des autres langages.
10.2 Activités dans des objets
10.2.1 Activité dans plusieurs fonctions
Examinons l'animation d'un vol d'oiseaux. On aimerait ne décrire le vol
que d'un oiseau et générer ensuite autant d'oiseaux qu'on veut pour le vol
complet. Pour chaque oiseau, le programme doit acher, l'une après l'autre,
les images d'un oiseau dont les ailes prennent les positions successives nécessaires au vol. Les images seront placées à des positions chaque fois un peu
plus loin vers la droite (programme Birds disponible sur le site du livre).
Pour commencer, on va utiliser la technique de l'exercice 3.6.3 déjà mentionné, mais on pourra ensuite simplier le concept. En plus de ce qui était
proposé dans cet exercice, il faut maintenant mémoriser une position et un
numéro d'image par oiseau. On va donc placer les caractéristiques de chaque
oiseau dans un objet qui lui est attribué.
Pour que les oiseaux soient décalés dans le temps, chacun d'eux attend un
temps aléatoire avant de partir. Chaque oiseau avance en battant des ailes de
gauche à droite de la fenêtre, puis recommence ces étapes : attente aléatoire
puis traversée de la page. Chaque étape est décrite dans une méthode.
L'utilisation de setTimeout est particulière dans un objet, car cette fonction ne connaît pas le concept d'objet et on doit avoir recours au concept
de fermeture (section 8.5). La fonction dénie dans setTimeout utilise la variable that, qui a été initialisée à this, pour retrouver l'objet. Notez qu'on ne
peut pas utiliser this directement, car c'est un mot réservé, pas une variable
locale.
1
2
3
4
5
function BirdObj (imgId) {
var x = 0
var imgNb = -1
var that = this
setTimeout( function() that.run() , 0)
10.2.
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
ACTIVITÉS DANS DES OBJETS
167
/////////////// première étape ////////////
this.run = function () {
var dt = Math.floor(Math.random()*3000)
setTimeout( function() { that.run1() }, dt)
}
/////////////// deuxième étape ///////////
this.run1 = function() {
x += 20
imgNb = (imgNb+1) % 7
document.getElementById(imgId).src = birdsImg[imgNb].src
document.getElementById(imgId).style.left = x+"px"
if (x<1200) {
setTimeout( function() { that.run1() }, 50)
} else {
x=-280
setTimeout( function() { that.run() }, 0)
}
}
}
/////////////// initialisation ///////////
new BirdObj("b1")
new BirdObj("b2")
new BirdObj("b3")
Les attributs de l'objet (x, imgN b) sont locaux et ne sont donc visibles
que par l'oiseau géré par l'objet. La ligne 5 enregistre l'oiseau dans le manager des temporisateurs à la création de l'objet. La fonction de la ligne 7
programme l'appel de la fonction de la ligne 12 pour qu'elle s'exécute après
un délai aléatoire. La fonction de cette ligne se rappelle elle-même jusqu'à ce
que l'oiseau ait traversé la page. Puis elle rappelle à nouveau la fonction de
la ligne 7 (on aurait pu faire un appel direct, mais ces appels diérés vont
nous permettre d'expliquer le concept de thread).
Les créations d'objets placés aux lignes 26 à 28, créent les objets et leur
passent l'identicateur de la balise qu'ils devront utiliser pour acher leur
image.
Le premier appel à la fonction run à la ligne 5 du constructeur démarre
une séquence de setTimeout qui doit resté entretenue par les appels renouvelés dans chaque méthode. Ainsi, on ne perd pas le l de l'histoire, ou
168
CHAPITRE 10.
APPLICATIONS PSEUDO-PARALLÈLES
thread. C'est le nom donné à une telle séquence. Dans l'application ci-dessus,
on a donc un thread pour chaque oiseau. La conception de ces threads fait
que le système ne les mélange pas et peut les gérer indépendamment les uns
des autres.
10.2.2 Activité gérée par un switch
La construction du paragraphe précédent devient compliquée si le nombre
de méthodes run augmente. On peut simplier l'approche au moyen de la
technique ci-dessous qui est plus systématique et peut même être traitée
automatiquement, comme nous le verrons dans la section suivante.
Le listing ci-dessous implémente le même comportement que l'objet du
paragraphe précédent.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
this.run = function () {
for(;;) {
switch (state) {
case 0:
var dt = Math.floor(Math.random()*3000)
state = 1
setTimeout( function() { that.run() }, dt)
return
case 1:
x += 20
imgNb = (imgNb+1) % 7
document.getElementById(imgId).src=birdsImg[imgNb].src
document.getElementById(imgId).style.left=x+"px"
if (x<1200) {
setTimeout( function() { that.run() }, 50)
return
} else {
x = -280
state = 0
break
}
}
}
}
10.3.
OBJETS ACTIFS
169
Plutôt que de passer de fonction en fonction, on passe de case en case.
La variable state indique le prochain case à eectuer. Lorsque le programme
case arrive à la n d'un case, on met la variable state à la valeur du prochain
case qu'il faudra exécuter et l'on retourne au système (lignes 8 et 16) pour
laisser les autres threads s'exécuter.
A la ligne 2, on a placé un for inni. Lorsque l'on eectue un return, ce
for n'a pas d'eet. Par contre le break de la ligne 20 sort du switch, arrive
à la n du for et reboucle sur le switch. Comme state a été mise à 0 (ligne
19), le switch reprend au case 0 et reprogramme un appel diéré.
Ainsi on peut sauter directement d'un case à l'autre soit directement,
soit en diéré. La variable state représente donc l'avancement du programme
(sorte de compteur de programme).
10.3 Objets actifs
La méthode présentée au paragraphe précédent se généralise et est utilisée dans la suite de ce cours pour créer une extension de Javascript qui
implémente un concept de thread proche de ce qu'on trouve dans d'autres
langages. On va donc écrire un programme qui comprend quelques nouvelles
instructions, inconnues de Javascript, puis on les convertira en un programme
Javascript standard au moyen d'un compilateur. Ce programme standard fait
appel aux fonctions d'une librairie particulière dénie dans kernel.js.
Le programme ci-dessous décrit le programme de l'oiseau au moyen de
ces nouvelles instructions (Source dans Birds).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
process Bird(nom, myNb) {
var x = 0
var imgNb = -1
this.run = function() {
for(;;) {
var dt = Math.floor(Math.random()*3000)+1000*myNb
waituntil(now()+dt)
for (x=-280;x<1200;x+=20) {
waituntil(now()+50)
imgNb = (imgNb+1) % 7
var dIm = document.getElementById('b'+myNb)
dIm.src = birdsImg[imgNb].src
dIm.style.left = x+"px"
}}}}
170
CHAPITRE 10.
APPLICATIONS PSEUDO-PARALLÈLES
A la ligne 1, on a remplacé le symbole function par process, pour indiquer
qu'il s'agit d'un constructeur d'objet spécial. Le compilateur va savoir qu'il
doit traiter ce constructeur, mais va simplement remplacer le symboleprocess
par function.
Les lignes 7 et 9 comportent la nouvelle instruction waituntil qui n'existe
pas en Javascript, mais qui sera transformée par le compilateur en utilisant
le principe décrit dans la paragraphe précédent. Cette instruction indique
qu'il faut suspendre l'exécution de ce thread jusqu'à ce que le temps now()
+ dt ou now() + 50 soit atteint, c'est-à-dire après un délai de dt ou de 50
millisecondes.
Le for de la ligne 5 implique que le programme de l'oiseau est exécuté indéniment. En lisant le programme ci-dessus, on voit bien que le programme
fait une petite attente, puis exécute la boucle de la ligne 8 jusqu'à ce qu'il
ait traversé la page. Puis il retombe sur la boucle innie et répète donc indéniment le même comportement. Dans les langages qui ont des threads, il
est possible de suspendre ainsi le thread pour des durées prédénies.
La commande waituntil doit se trouver dans une méthode run, et non
dans le code du constructeur, ni dans une autre méthode.
10.3.1 Le compilateur
Le code ci-dessus peut être écrit dans un chier terminé par .sjs ou .js et
compilé dans LemanOS en maintenant le bouton gauche de la souris enfoncé
sur le chier comprenant ce code et en sélectionnant le menu Compiler sJS,
ou en sélectionnant son onglet et en choisissant Compiler sJS dans le le menu
Fichier. Un chier .js contenant le code transformé est créé. Ce chier est
visible dans le répertoire de LemanOS et doit donc être importé dans votre
application au moyen d'une balise <script src="chier.js">. Vériez que
le signe OK apparaisse à côté du numéro de ligne en haut à droite de la
fenêtre de LemanOS lors de la compilation. Attention, pour voir la dernière
version du chier, il faut le rafraîchir au moyen du menu proche de l'appel
au compilateur. La gestion des temporisateurs et des concepts qui suivent
sont dénis dans la librairie /LemanOS/kernel.js qui doit être importée de
la même façon que votre chier.
Pour information, voici quelques détails de la façon dont un compilateur
crée la source d'un programme. Tout d'abord, un compilateur doit pouvoir
utiliser des instructions jump ou go to. Celles-ci sont le stéréotype de la
mauvaise programmation, pour diérentes raisons dont la discussion nous
mènerait trop loin, mais elles forment l'instruction élémentaire la plus utilisée
de tout ordinateur.
10.3.
OBJETS ACTIFS
171
En fait l'instruction break de la ligne 20 du paragraphe 10.2.2, en collaboration avec le switch et le for inni réalise un goto acceptable qui permet
de passer d'un case à l'autre, en avant ou en arrière.
Le test ci-dessous contient une instruction waituntil(). Pour la réaliser,
il faut donc segmenter la fonction au moyen d'une des techniques vues aux
paragraphes 10.2.1 et 10.2.2. La source à gauche est traduite comme indiqué
à droite :
1
this.run = function() {
2
3
4
if (x>0) {
5
6
7
waituntil(now()+10)
8
9
10
x = 10
11
12
13
14
15
}
}
x=0
this.run = function() {
for(;;) {
switch(_state) {
case 10000:
if (!(x>0))
{_state=10002;break}
_state = 10001
setTimeout(that.run, 10)
return
case 10001:
x = 10
case 10002:
x = 0
break
} } }
Le test de la ligne 5 est transformé en un test qui saute le code si son
expression est fausse (le compilateur ajoute donc systématiquement une négation devant la condition). Le saut est fait en mettant la variable _state
à la valeur du case auquel on veut aboutir et en codant un break. Le programme va ainsi sortir du switch arriver sur la boucle for innie, puis entrer
à nouveau dans le switch et nalement aboutir au case visé.
Si la condition est vraie, le programme va continuer, transmettre une
demande au temporisateur de le rappeler plus tard, mettre la valeur de la
variable _state à la valeur correspondant au case à exécuter après le délai
et, cette fois-ci, sortir de la fonction au moyen de return.
Pour programmer un saut à une autre partie du programme, il faut donc
mettre la variable _state à la valeur correspondant au prochain case, puis
coder soit un return pour suspendre l'exécution pendant un certain temps,
soit un break, pour continuer tout de suite.
On peut décomposer les instructions for, while et même switch de la
même façon et ainsi créer un environnement pseudo-parallèle. Notez que le
172
CHAPITRE 10.
APPLICATIONS PSEUDO-PARALLÈLES
moteur Javascript du navigateur fait un travail très semblable pour le code
standard, mais génère des instructions élémentaires qui font directement des
sauts (malheureusement sans donner la possibiliter d'interrompre le ux du
programme pour le reprendre plus tard).
Cette approche a par contre quelques limitations par rapport à ce qu'on
trouve dans un langage vraiment multi-tâche. Les variables locales à la fonction this.run ne sont pas gardées d'une exécution à l'autre. Cela n'est pas
grave, puisqu'il sut de les placer parmi les attributs de l'objet. Il n'est pas
possible de mettre un waituntil dans une fonction elle-même appelée par
this.run, ou alors il faudrait décomposer la nouvelle fonction, ce qui compliquerait les choses, mais le jeu n'en vaut pas la chandelle. Nous nous contenterons donc de placer les interruptions au niveau de this.run. D'autre part ni
les instructions break ni continue ne sont traduites.
10.4 Communications entre objets
10.4.1 Appel d'un objet actif
Les concepts des paragraphes précédents permettent d'entrelacer les instructions de plusieurs tâches, tels les instructions qui déplacent et modient
les images des oiseaux. En fait, dans des applications plus complexes, il faut
pouvoir échanger des informations et des données entre les diérentes tâches
(ici les oiseaux). Dans cette section, on va montrer un moyen de réaliser cela
au moyen d'un concept original, tirés des méthodes de modélisation CSP
(Concurrent Sequential Processes, de C.A.R. Hoare) et CCS (Calculus of
Communicating Systems, de R. Milner) et déjà partiellement appliqué au
langage Ada et plus complétement à Occam et à quelques langages dérivés.
Nous supposons donc maintenant que les oiseaux doivent prendre un paquet avec eux durant leur vol. Les paquets ne sont pas toujours disponibles et
les oiseaux doivent parfois attendre qu'un paquet soit prêt avant de l'obtenir.
Les paquets sont délivrés par l'objet distributeur sur appel de sa méthode
prendre. Le distributeur n'accepte l'exécution de cette méthode que si un
paquet est prêt, sinon l'appelant est bloqué sur cet appel jusqu'à ce que la
méthode soit acceptée par le distributeur.
L'objet actif distributeur est codé ci-dessous. Il comporte la méthode
prendre qui est semblable à celle d'un objet normal et la méthode run, semblable à celle des oiseaux et qui progresse donc indépendemment des autres
objets actifs. Le distributeur attend un temps aléatoire représentant la préparation d'un paquet, dans sa méthode run, puis accepte un appel sur la
méthode prendre. Il attend alors lui-même que la méthode soit appelée et
10.4.
COMMUNICATIONS ENTRE OBJETS
173
exécutée avant de poursuivre son chemin.
Si un oiseau appelle prendre avant que le distributeur accepte cette méthode, l'oiseau est bloqué. Si le distributeur accepte la méthode avant qu'un
oiseau appelle prendre, le distributeur est bloqué sur l'appel à accept. Ainsi,
on a un rendez-vous entre les deux objets actifs. La méthode prendre est
exécutée pendant ce rendez-vous, puis les deux objets poursuivent leurs
threads indépendamment l'un de l'autre jusqu'au prochain rendez-vous éventuel (Source : BirdsPassage).
1
2
3
4
5
6
7
8
9
10
11
12
process Distributeur(nom) {
var i
this.prendre= function () {
var p = document.getElementById('p'+i)
p.style.visibility = "visible"
return p
}
this.run = function () {
for (i=0;i<6;i++) {
waituntil(now()+Math.floor(Math.random()*2000))
accept prendre
}}}
Dans le programme principal :
13
distributeur = new Distributeur("distr")
Dans l'oiseau :
14
paquet = distributeur.prendre()
Les lignes 3 à 7 décrivent une méthode tout à fait normale qui trouve une
image de paquet, la rend visible et la retourne à l'appelant. Les lignes 8 à 12
décrivent les instants et conditions où la méthode est acceptable et peuvent
contenir un travail de préparation indépendemment des appelants.
L'appel distributeur.prendre() de la ligne 14 doit être traîté par le compilateur susmentionné pour implémenter la synchronisation expliquée ci-dessus.
Elle doit se trouver dans une méthode run, et non dans le code du constructeur, ni dans une autre méthode.
Remarque : Le compilateur suppose que toutes les méthodes actives
sont situées dans le chier compilé et génére un appel synchrone pour tous
les appels à ces méthodes. Il ne faudrait donc pas déclarer une méthode
normale et une méthode synchrone avec le même nom !
174
CHAPITRE 10.
APPLICATIONS PSEUDO-PARALLÈLES
10.4.2 Boîte aux lettres
Le rendez-vous présenté ci-dessus est une fonction de base indispensable,
mais utilisée seule elle est trop restrictive, dans le sens, par exemple, où
l'oiseau ne pourrait pas prendre le premier paquet disponible dans un distributeur parmi deux ou plusieurs, sans savoir lequel sera prêt le premier. Dans
ce cas, il faut utiliser l'instruction select, achée ci-dessous :
1
2
3
4
5
6
7
8
select {
case
paquet = distributeur1.prendre() {
. . .
case
paquet = distributeur2.prendre() {
. . .
}
Cette instruction bloque le programme si aucun des appels n'est prêt,
puis fonctionne comme un switch (mais ne nécessite pas de break ), c'est-àdire que dès qu'un appel a été accepté, l'objet exécute les instructions qui
suivent l'appel accepté puis continue son chemin après la n du select. L'appel
ignoré ne sera exécuté que si le programme revient sur ce select plus tard et
que le premier appel n'est pas à nouveau prêt. Notez que si ce dernier cas
arrivait, cela signierait que le programme est surchargé.
Cette instruction ne fonctionne que dans la méthode run, évidemment.
Elle peut par contre contenir plusieurs appels, plusieurs accept et plusieurs
waituntil. L'exemple suivant présente une boîte aux lettres ou canal, un
concept fondamental en programmation multi-tâche. Cet objet accepte qu'on
dépose des paquets à tout moment, mais qu'on n'en prenne que s'il y en a
au moins un de disponible (Source : BirdsParcel).
10.4.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
COMMUNICATIONS ENTRE OBJETS
175
process Poste(nom) {
this.deposer = function (paquet) {
this.fifo.push(paquet)
}
this.prendre= function () {
return this.fifo.shift()
}
this.fifo = [ ]
this.run = function () {
for (;;) {
if (this.fifo>0) {
select {
case
accept deposer
case
accept prendre
}
} else {
select {
case
accept deposer
}
}
}
}
}
Si le fo contient au moins un paquet, la boîte aux lettres exécute le select
de la ligne 12 qui accepte soit prendre, soit deposer. Par contre, quand il n'y a
rien dans le fo, elle exécute le select de la ligne 19 qui n'accepte que deposer.
Il est possible d'écrire cela d'une manière plus compacte :
176
1
2
3
4
5
6
7
8
CHAPITRE 10.
APPLICATIONS PSEUDO-PARALLÈLES
for (;;) {
select {
case
accept deposer
case
when (this.fifo>0) accept prendre
}
}
La ligne 6 n'est active que si la condition du when est vraie. Il est évidemment possible de mettre des when devant les accept, les appels et les
waituntil.
Ainsi, si l'on place le select dans la boîte aux lettres, elle bloquera les appelants si elle est pleine, c'est-à-dire si elle contient un nombre prédéterminé
de messages.
Le code ci-dessous n'accepte plus les appels à deposer quand il y a n0
paquets ou plus. Cependant, elle accepte évidemment soit l'un soit l'autre
des appels. Si ce n'était pas le cas, elle se bloquerait irrémédiablement.
1
2
3
4
5
6
7
8
for (;;) {
select {
case
when (this.fifo<n0) accept deposer
case
when (this.fifo>0) accept prendre
}
}
10.4.3 Verrou
Supposons maintenant, pour les besoins de la démonstration, que les oiseaux utilisés précédemment doivent passer par un couloir, par exemple pour
entrer dans un pigeonnier (Source : BirdsCouloir). Un seul oiseau peut se
trouver dans le couloir à un instant donné. Pour entrer, un oiseau doit passer
une porte et pour sortir, il en franchit une autre. Il faut que le prochain oiseau
ne puisse ouvrir la porte d'entrée que lorsque le précédent est sorti. Le programme suivant coordonne les actions d'entrées et de sorties, en commandant
la séquence possible des appels au moyen d'accept.
10.4.
1
2
3
4
5
6
7
8
COMMUNICATIONS ENTRE OBJETS
177
process Couloir(nom) {
this.entrer = function() {
this.sortir = function() {
this.run = function() {
for (;;) {
accept entrer
accept sortir
}}}
Quant aux oiseaux, on va simplement leur demander (lignes 8 à 15) d'entrer dans le couloir lorsque la valeur de x vaut 100, en sortir lorsqu'elle vaut
800, et attrendre un instant comme auparavant sinon.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
process Bird(nom, myNb) {
var x = 0
var imgNb = -1
this.run = function() {
for(;;) {
var dt = Math.floor(Math.random()*3000)
for (x=-280;x<200;x+=20) {
// affiche le déplacement de l'oiseau {
}
couloir.entrer()
for (x=220;x<700;x+=20) {
// affiche le déplacement de l'oiseau {
}
couloir.sortir()
for (x=720;x<1100;x+=20) {
// affiche le déplacement de l'oiseau {
}
}}}}
L'objet créé par le constructeur Couloir s'exécute jusqu'à l'instruction
accept de la ligne 6, où il attend qu'un appel de la méthode entrer soit
eectué. L'appel à sortir reste alors bloquant : l'exécution d'un processus
qui appellerait cette méthode serait suspendue jusqu'à ce que l'objet couloir
arrive à la ligne 7 et accepte cette méthode.
178
CHAPITRE 10.
APPLICATIONS PSEUDO-PARALLÈLES
Considérons maintenant le programme d'un oiseau. Tous les oiseaux avancent et à un moment donné, un oiseau arrive à la ligne 10. Il appelle entrer
dans le couloir, ce qui permet à l'objet couloir d'avancer. Ce dernier se bloque
maintenant sur l'instruction de la ligne 7, et ce sont les appels sur entrer qui
sont désormais bloqués. Les oiseaux qui arrivent maintenant sur la méthode
entrer du couloir sont bloqués. Quand l'oiseau du couloir arrive à la ligne 14,
il appelle la méthode sortir, elle est acceptée instantanément par le couloir
et l'oiseau continue son vol. Le couloir boucle maintenant sur l'acceptation
de la méthode entrer, ce qui permet à un des oiseaux suivants (un seul) de
pénétrer dans le couloir. Et ainsi de suite.
L'objet couloir a ce qu'on appelle une fonction de verrou. Il assure, pour
autant qu'on l'utilise de façon correcte, qu'au maximum un objet pénètre
dans une zone sensible. Ceci est souvent crucial dans les applications multitâches. Par exemple, il pourrait arriver qu'un certain nombre de processus
traitant des usagers en parallèle doivent écrire des données fournies par ces
usagers dans un chier. Si chaque processus lit le chier en mémoire, ajoute
ses informations, puis réécrit le chier, il pourrait arriver que deux processus
lisent le chier à peu près au même moment, écrivent leurs informations à
la suite des données actuelles en mémoire et réécrivent ces contenus dans
le chier. Le deuxième qui écrit eacera donc les informations du premier,
peuisqu'elles n'étaient pas encore présentes au moment où il a eectué sa
lecture. En eectuant les opérations de lecture, modication et réecriture
entre les appels entrer et sortir d'un verrou, les deux processus ouvriront et
refermeront le chier l'un après l'autre. Le deuxième devra donc attendre,
mais si ces opérations ne durent pas longtemps et si les accès quasi-simultanés
sont peu fréquents, le verrou ne ralentit pas l'exécution, mais assure que les
données seront traitées correctement, quoiqu'il arrive.
10.4.4 Connexion avec les éléments HTML
Les fonctions appelées par les éléments de HTML ne peuvent pas appeler
les méthodes des objets actifs directement, car si la méthode est bloquante
au moment de l'appel, tout les threads seraient bloqués. Pour faire la liaison
entre un élément HTML et un objet actif, il faut employer des pseudo-objets
dont on donne un exemple pour un bouton et un pour un champ de texte.
process Bouton(nom) {
this.clic = function() { }
this.accepteClic = function(data) {
accept clic
}
10.5.
EXERCICES
179
}
bouton = new Bouton("clic")
<button onclick="bouton.accepteClic()">
Le code ci-dessus ressemble à un objet actif, mais il ne contient pas de
méthode run. Dans ce cas, il n'y a pas d'eet de thread, et l'instruction
accept placée dans la méthode acceptClic n'est pas bloquante. Les autres
objets actifs appellent la méthode clic de l'objet bouton exactement comme
ils le feraient pour un objet actif normal. Ils sont bloqués si l'instruction
accept clic n'a pas été eectuée et débloqués si elle a été appelée depuis la
dernière exécution de clic(). Si l'on clique plusieurs fois le bouton sans qu'il
y ait d'appel à clic, ces appels n'ont pas d'eet.
process EntreeTexte(nom) {
this.entrer = function() { }
this.data = ""
this.accepteEntrer = function(data) {
this.data = data
accept entrer
}
}
entree = new EntreeTexte("clic"')
<input onclick="entree.accepteEntrer(this.value)">
Dans l'exemple ci-dessus, on a ajouté une variable qui contient la valeur
du champ de texte. Si l'élément appelle plusieurs fois la méthode accepteEntree, seule la dernière valeur du champ est transmise au programme, ce qui
est en fait le comportement le plus consistent.
10.5 Exercices
10.5.1 ex - Mouvements de billes
Essaim de billes
Reprendre l'exercice 3.6.3 représentant une bille qui voyage dans un rectangle, mais cette fois-ci en faisant bouger plusieurs billes dans des objets
actifs.
Choisir une zone circulaire à l'intérieur du rectangle qui ne doit recevoir
qu'une bille à la fois en utilisant un verrou.
180
CHAPITRE 10.
APPLICATIONS PSEUDO-PARALLÈLES
10.5.2 ex - St-Nicolas
On veut représenter l'organisation caricaturée d'une usine de jouets. StNicolas a des petits nains qui fabriquent des jouets et des rennes qui les
portent à domicile. Les nains doivent entrer à trois dans une salle pour créer
un jouet, ce qui prend un certain temps. Les rennes peuvent tous entrer dans
une étable pour attendre le prochain paquet à livrer.
Quand les trois nains ont terminé le jouet, ils le transmettent à St-Nicolas
et sortent de la salle pour laisser entrer les trois suivants. Ils y reviendront
plus tard. St-Nicolas prend le paquet et fait sortir un renne lui transmet le
paquet et le laisse partir.
On peut utiliser les salles comme objet actifs pour compter les rennes et
les nains. On peut utiliser les salles comme intermédiaire entre St-Nicolas et
les rennes ou les nains, par exemple de la façon suivante. St-Nicolas et les
rennes font les appels correspondants.
select {
case
accept enter // un renne entre
n++
case when (n>0)
accept go // St-Nicolas laisse partir un renne
n-accept exit // un renne part
}
Dans la solution qui vous est proposée, on a mis des images qui avancent
comme les oiseaux, (mais sans mouvement).

Documents pareils