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).