Mémo JavaScript
Michel Llibre - Mai 2005
Ce mémo est à l'image de javascript : un gros fouillis où j'ai essayé d'accumuler un maximum des connaissances que j'ai dû apprendre pour mettre en oeuvre ce langage, afin de pouvoir les retrouver plus rapidement.
Pour faire des pages Web ne contenant que du texte et quelques images, sans interaction avec l'utilisateur, HTML seul est suffisant, mais pour faire des applications intercatives avec des boutons, des cases à cocher... javascript devient rapidement incontournable.
Pour apprendre le JavaScript, il n’y a besoin d’aucun système de programmation. Il suffit d’avoir un navigateur Web (Firefox, Edge, Chrome, Opera...) dans lequel s’affichera le résultat du programme, et un éditeur de texte pour écrire les programmes. Quel éditeur : J’en conseillerais deux :
•Notepad++ : très simple, indispensable, mais il n’offre pas d’aide sur le langage
•Visual Studio de Microsoft : Éditeur extrêmement puissant avec une aide à la complétion (après le début de la frappe, il propose différents compléments compatibles), mais il est très lourd à mettre en œuvre. On génère l’affichage de la page en cours d’édition par CTRL+SHIFT+W.
•Visual Studio Code de Microsoft : Idem Visual Studio, tout court. Légèrement moins puissant.
Comme JavaScript a été conçu pour modifier des pages Web, la manière le plus simple pour l’essayer consiste à créer une page Web. Les pages Web les plus simples sont écrites en langage HTML. Voici le contenu du fichier simple.html qui génère une page avec pour seule phrase "Une page Web très simple" :
<!DOCTYPE html>
<html>
<body>
Une page Web très simple
</body>
</html>
La première balise <!DOCTYPE html> peut être omise pour des essais en local, mais sa présence est conseillée.
Le contenu de tout le reste du fichier HTML doit être entouré par les deux balises <html> et </html>.
Le contenu de ce qui va être affiché sur la page Web doit être entouré par les deux balises <body> et </body>. Le Langage HTML est un langage qui utilise ces couples de balises ouvrante et fermante (avec le caractère / avant le nom de la balise fermante). Les espaces supplémentaires et les retours à la ligne n’ont aucune importance dans le langage HTML. Ils sont ignorés. On peut en rajouter, ou les enlever. Le résultat sera inchangé. Par exemple le fichier précédent pourrait être écrit comme suit :
<!DOCTYPE html><html><body>Une page Web
très simple</body></html>
et le résultat sera identique. C’est tout ce qu’il y a à connaître sur le HTML pour le moment.
Du code javascript peut-être écrit à l'intérieur d'un fichier html et/ou inséré dans ce fichier à l'aide de la ligne de code suivant :
<script src="fichier.js"></script>.
Cette ligne, ou ces lignes s'il y en a plusieurs, sont généralement placées à la fin du fichier pour laisser à la page html du temps de dessiner ses éléments pendant le téléchargement du fichier fichier.js car le javascript est généralement utilisé pour modifier l'agencement de la page. Cependant on peut les mettre n'importe où, et même avant la balise <body>, généralement à l'intérieur des balises <head>. Dans ce cas le téléchargement se fait et bloque le code HTML qui construit la page jusqu'à ce que le fichier soit entièrement réceptionné. On peut suppimer ce blocage en différant ce chargement pour qu'il ne se charge qu'après le dessin de la partie html. Ceci est fait en ajoutant l'attribut defer :
<script src="fichier.js" defer></script>
Dans ce cas le script se charge en arrière plan puis s'exécute quand la partie HTML est complètement affichée.
Une autre possibilité est offerte par le mode async, qui ne bloque pas la construction HTML, mais où le script n'est pas différé à la fin comme avec defer, mais est chargé en parallèle en arrière plan et il s'exécute dès qu'il est prêt.
<script src="fichier.js" async></script>
La pratique standard étant de mettre la ligne sans attribut dans le <body> avant la balise de fin </body>
Un programme JavaScript minimaliste est constitué de la ligne suivante, que l’on sauvegardera dans un fichier que nous nommerons coucou.js :
window.document.write("Bonjour tout le monde !");
Pour exécuter ce fichier JavaScript nous l’incluons dans un fichier monjs.html que l’on crée pour cet essai :
<!DOCTYPE html>
<html>
<script src="coucou.js"></script>
</html>
et si on exécute le fichier html monjs.html , on obtiendra dans un navigateur la ligne suivante :
Bonjour tout le monde !
Dans cet exemple, on a écrit du programme JavaScript dans un fichier JavaScript (*.js), mais on peut aussi écrire du programme JavaScript directement à l’intérieur d’un fichier html. Par exemple comme ceci :
<!DOCTYPE html>
<html>
<script>
document.write("Bonjour tout le monde !"); </script>
</html>
où la partie écrite en langage JavaScript est insérée entre les balises <script> et </script>.
On a omis de préciser le conteneur window devant document.write. Comme window est le conteneur de tous les éléments, il est toujours sous-entendu et pratiquement toujours omis.
Remarque : Cette procédure d’écriture dans une page Web utilisant la méthode document.write est fortement dissuadée car on maîtrise mal l’emplacement où se fera cette écriture. Elle risque d’écraser du contenu dans la page, ou bien ne pas être visible. Mais sur des pages vierges elle est très utile pour les besoins de l’apprentissage du langage.
Par contre, la ligne suivante (bien qu'un peu lourde) est parfaite pour insérer rapidement du texte dans une page web :
document.body.append(document.createTextNode("Coucou"));
Elle ajoute à la suite des éléments précédemment insérés dans le <body> un noeud de texte qui n'appartient à aucun élément HTML standard (div, p, ...).
Pour mettre au point un programme, on utilise souvent des impressions de résultats intermédiaires
qui ne s’affichent pas sur la page Web, mais qui sont envoyées dans une console de débogage, au moyen de l’instruction :
window.console.log("Texte vers sur la console");
Ici log est la fonction d’écriture que met en œuvre l’objet console qui est un élément de la fenêtre du navigateur (window).
Exemple :
<!DOCTYPE html>
<html>
<body>
<script>
window.console.log("Début programme");
document.write("Bonjour tout le monde !");
window.alert("Coucou !"); // Texte envoyé à une fenêtre popup
window.console.log("Fin programme");
window.console.log("JavaScript, c’est super !!"); </script>
</body>
</head>
</html>
Sur la page web, seule la ligne Bonjour tout le monde ! sera écrite. Pour voir toutes les impressions il faut ouvrir la console de débogage. La procédure pour y accéder dépend du Navigateur : CTRL+MAJ+I ou CTRL+MAJ+J, à essayer.
Dans les consoles Chrome et Edge on va trouver les lignes suivantes : DÊbut programme
Fin programme
JavaScript, c’est super !!
alors que dans la console Firefox le mot Début sera bien écrit : Début programme
Fin programme
JavaScript, c’est super !!
Pour que les caractères accentués soient bien écrits dans tous les navigateurs, il faut bien préciser le type de codage utilisé (utf-8 par exemple) car Chrome et Edge prennent par défaut le codage des consoles systèmes de la machine qui ne comprend pas les caractères accentués. On ajoutera la ligne suivante :
<head><meta charset="utf-8"/></head>
au début du fichier html, comme ceci :
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"/></head>
<body>
<script>
console.log("Début programme");
document.write("Bonjour tout le monde !"); console.log("Fin programme");
console.log("JavaScript, c’est super !!"); </script>
</body>
</head>
</html>
La zone entre les balises <head> et </head> sert à préciser divers paramètres qui ne nous intéressent pas pour l’apprentissage de JavaScript (l’auteur de la page, la date de la rédaction...), à l’exception de la balise <meta ... /> que l’on utilise ici pour indiquer le codage utilisé pour les caractères.
La console de débogage est interactive. On peut y taper du JavaScript qui est interprété ligne après ligne. Exemple :
> x=1 < 1 > y=2 < 2 > x+y < 3 > console.log("Hello") < Hello Ce qui est après "> " est tapé par nous, et ce qui est après "< " est la réponse du PC.
charger (load) : Utilisé pour décrire l'action de récupérer et afficher une ressource dans le navigateur.
télécharger (download): Utilisé pour décrire l'action de récupérer et enregistrer une ressource sur le disque local.
Upload : Envoyer un fichier vers un serveur distant.
instance : c'est un objet représentant d'une classe qui est manipulé à l'aide d'un identifiant choisi par le programmeur. Exemple : let t1 = new Date(); let t2 = t1; Ici t1 et t2 sont deux objets Date. Ce sont deux instances de Date.
callback : On emploie très rarement "Fonction de rappel" en français. En langage C c'était un pointeur sur une fonction que l'on pouvait passer en argument d'une autre fonction, ce qui permettait d'appeller cette fonction depuis l'intérieur de l'autre. En javascript, comme les instances sont des références sur des objets ou fonctions, une instance de fonction, affectée à une autre variable ou passée en argument d'une fonction, peut être considérée comme une callback puisqu'on pourra l'appeler à l'aide de cette instance. Exemple :
function somme(x,y){ return x+y;} // On définit une fonction qui servira de callback
function execute(oper, x, y) {return oper(x,y);} // On la passe en argument d'une autre fonction
let r = execute(somme,2,3); // On exécute cette deuxième fonction avec comme argument notre callback.
console.log(r) // 5
asynchrone : Chaque instruction d'un programme réalise un travail et lorsque ce travail est terminé l'instruction suivante est exécutée. Si ce travail dure longtemps, l'instruction suivante n'est exécutée qu'après cette longue durée. C'est de la programmation synchrone. Pour éviter de trop grandes attentes à ne rien faire, on utilise un mode de programmation asynchrone où la première instruction lance le travail et définit une callback qui sera appelée lorsque le travail sera achevé, ce qui fait que le traitement local peut continuer pour faire autre chose, mais la continuité qui nécessite l'achèvement du travail demandé est déplacée à la suite de la callback.
Pour lire des données (tapées par l’utilisateur) dans un programme on peut utiliser la méthode suivante :
res = window.prompt(texte, datadef);
qui va ouvrir une fenêtre PopUp contenant le "texte" et une zone d’entrée dans laquelle on peut écrire notre donnée. L’argument facultatif datadef est la donnée qui sera prise par défaut si on referme la zone de donnée sans rien taper. La variable res contiendra le texte frappé ou datadef.
Exemple :
DOCTYPE html>
<html>
<head><meta charset="utf-8"/></head>
<body>
<script>
document.writeln("Bonjour tout le monde !"); x = prompt("Donner un nombre ", 0); y = prompt("Donner un autre nombre ", 0); nx = Number(x); s = nx + Number(y);
document.write("<br/>La somme " + x + " + " + y + " vaut : " + s.toString()); </script>
</body>
</head>
</html>
Cet exemple contient de nombreuses nouveautés. L’instruction : x = prompt("Donner un nombre ", 0);
produit la lecture du nombre frappé dans la fenêtre PopUp. Il est affecté à la variable x mais sous forme d’une chaîne de caractères (un texte), et de même pour la ligne suivante.
L’instruction :
nx = Number(x);
transforme la chaîne de caractère x en nombre nx., ce qui permet de faire des opérations arithmétiques sur lui. Ainsi la somme suivante génère le nombre s.
s = nx + Number(y);
Ensuite on fabrique un texte à écrire à partir de plusieurs parties : "<br/>La somme " : Le début de notre texte
•x : le premier texte lu dans la fenêtre PopUp
•" + " : le signe de l’addition
•y : le deuxième texte lu dans la fenêtre PopUp
•" vaut : " : la fin de notre texte
•s.toString() : le texte résultat de l’addition (un nombre). La conversion du nombre s en texte est réalisée à l’aide de la méthode toString(),
et ces différentes parties sont assemblées en un seul texte (on dit qu’elle sont concaténées) à l’aide de l’opérateur + en verts dans le texte qui suit :
"<br>La somme " + x + " + " + y + " vaut : " + s.toString() En JavaScript, les chaînes de caractères (on dit string en langage informatique) sont concaténées au moyen de l’opérateur +.
Lorsqu’on utilise cet opérateur + entre une chaîne de caractère et un autre élément (un nombre par exemple), cet autre élément est automatiquement converti en chaîne de caractère avec sa méthode toString() (qui existe pour tous les éléments de JavaScript). Il en résulte que l’on peut simplifier la dernière instruction en : document.write("<br/>La somme " + x + " + " + y + " vaut : " + s); car toString() sera automatiquement appelé sur s.
J’ai l’habitude de terminer chaque ligne d’instruction par un ";" comme dans les langages C, C++, C#.... Ceci n’est pas obligatoire en JavaScript. Toutefois, si on veut mettre plusieurs instructions sur une même ligne, il faudra les séparer par ce ";" comme dans : nx = Number(x); s = nx + Number(y);
Comme JavaScript est proche des langages C++, C#... il arrive souvent qu’on traduise l’un dans l’autre et réciproquement, et parfois des gros pans de codes s’écrivent exactement pareil dans les deux langages et ne sont pas à modifier, ce qui fera gagner du temps en particulier si le JavaScript à traduire dans un autre langage possède déjà les ";" de fin de ligne.
JavaScript utilise les mêmes séparateurs de commentaires que C/C++, C#... à savoir : x = 1 ; // on affecte la valeur 1 à la variable x
/* ci-après
on affecte la valeur 2
à la variable y */
y = 2 ;
Tout ce qui suit //, jusqu’à la fin de la ligne, est un commentaire Tout ce qui est entre /* et */ est un commentaire.
Remarque :
Dans un fichier html, dans une partie en pur html, les parties en commentaires sont entre "<!--" et "-→" comme ci-après :
<body><!-- cette partie
écrite de couleur verte est
un commentaire -→<h1>Jeu de la Vie</h1>…
Javascript s’exécute généralement dans une page web. On peut exécuter du javascaript pur à l’aide de l’application Node.js (qui facilite la mise en place d’un serveur http) mais c’est très particulier.
Dans la page web, il y a des parties écrites en pur html, comme par exemple : <img id=’idImg’ src="treasuremap.png"> instruction qui charge une image dans la page, et des parties écrites en javascript, comprises entre les balises <script> et </script>.
Avant l’affichage de la page web, et quelque soit l’ordre des écritures, les deux parties (html et script) sont entièrement lues (avant tout affichage). Dans javascript l’ordre des déclarations est sans importance. Une fonction placée au début peut utiliser des variables initialisées à la fin. Le seul effet de l’ordre est que pour toutes les occurrences où une variable est utilisée, dans un contexte donné, la valeur utilisée est celle de sa dernière assignation.
Lorsque le fichier est entièrement lu et chargé la page est affichée en fonction des dernières assignations effectuées.
Javascript est un langage événementiel. Les actions de l’usager sur le clavier ou avec la souris sont surveillées et mettent à jour un objet événement auquel on peut associer des fonctions de rappel (callbacks) pour réagir à ces actions et faire évoluer le contenu de la page affichée. Il existe également deux fonctions qui permettent de programmer des actions différées dans le temps ou répétitives. Lorsque l’intervalle de temps fixé est écoulé un événement survient qui active la fonction de rappel choisie à cet effet.
De loin le plus important est la page de la référence sur le langage JavaScript qui se trouve ici :
https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference
A la deuxième ligne de la page il y a la loupe qui permet de trouver les informations sur un élément quelconque du langage.
Mais pour apprendre le langage, le site suivant est excellent :
Tous les mots clés du langage javascript s’écrivent en minuscules. Ceux de la version ECMAScript 2015 sont : break, case, class, catch, const, continue, debugger, default, delete, do, else, export, extends, finally, for, function, if, import, in, instanceof, new, return, super, switch, this, throw, try, typeof, var, void, while, with, yield et pour le mode strict de javascript : implements, let, package, protected, static, interface, private, public.
Dans les parties en html, le nom des balises (tags) et des attributs est case-insensitive, c’est-à-dire qu’on peut mélanger majuscules et minuscules, mais il est fortement conseillé de tout mettre en minuscules, car certains navigateurs font cette conversion.
Pour les noms donnés aux variables JavaScript il vaut mieux se limiter aux caractères latins a-Z, A-Z, 0-9 et les caractères $ et _ (underscore). Le nom ne doit pas commencer par un chiffre. Commencer par un $ ou un _ est également déconseillé car cela est généralement réservé à certaines variables systèmes. Pour les noms compliqués, la convention "lower camel case" (petit dos de chameau) est conseillée, à savoir ceciEstUnNomComplique, avec une majuscule en début de chaque entité significative sauf pour la première.
JavaScript est un langage orienté objet avec des types plus ou moins complexes, mais ce typage est dit faible car une même variable peut changer de type au gré des affections :
x = 12 ; // x est un nombre
x = "coucou" ; // x devient un string
x = Math.sqrt ; // x devient la fonction racine carrée : x(16) ==> 4
Le mot clé typeof renvoie le type l’élément qui suit. Exemple : typeof "123"+456 renvoie ‘string’.
Les principaux types primitifs (prédéfinis par le langage) JavaScript sont :
•Boolean : x = true ; x = false ;
•Nul de valeur unique : x = null ;
•la valeur undefined pour une variable de type quelconque ou indéfini et a qui on n’a pas affecté de valeur
•NaN ayant une valeur indéfinie : x = 1/0 ;
•Number pour tous les nombres ordinaires (entiers, réels, ...) : x = 2/3.5 ;
•BigInt pour les entiers sans limite de valeur : x = 123n ; // Remarquer le n à la fin
•String pour les chaînes de caractères : x = "toto" ; y =’titi’ ;
•Symbol pour les variables protégées par un mécanisme garantissant leur unicité, afin d’éviter des conflits avec des variables définies par d’autres sources.
On retiendra surtout les deux principaux types primitifs qui sont les nombres (Number) et les textes ou chaînes de caractères (String), mais aussi les booléens vrai et faux (Boolean true et false).
Le casting est le changement de type d’un résultat. Dans les expressions, quand les opérandes ne sont pas du même type, javascript opère un casting automatique vers le type de plus haut niveau :
10 + "toto" ==> "10toto"
false + bibi" ==> "falsebibi"
Chaque fois que des types incompatibles sont associés par un opérateur, JavaScript essaie de faire une conversion qui rend l’opération possible. Si plusieurs conversions sont possibles avec des résultats différents c’est la conversion du plus simple vers le plus complexe qui est réalisée :
"" + true+3 ==> true3 // Le booléen true devient le string "true" "" + (true+3) ==> 4 // Le booléen true est converti en 1 Contrairement au C++ et PHP, javascript ne possède pas les conversions explicite de type (int).., (float)... mais propose les fonctions spécialisées suivantes :
Fonction |
convertit vers |
parseInt() |
Int, Integer |
Boolean() |
Boll, Boolean |
parseFloat() |
Float, Double, Real |
String() |
String |
split() |
Array |
Les variables peuvent être assignées sans avoir été déclarées : a = 34 ; On peut les déclarer (en leur assignat une valeur ou pas) avec les mots clés let, var et const. On verra plus loin que ces mots influent sur la portée de la variable (l’endroit où elle est accessible) : let a = 27 ; var b = 38 ; const c = window ;
Les variables peuvent être réaffectées à n’importe quoi d’autre sauf les variables const qui ne peuvent plus être affectées à autre chose (c sera toujours un alias de window) , mais leur valeur peut varier (comme l’objet window qui évolue en permanence).
Dans javascript tout est objet : les variables simples, les fonctions, les classes...
Si on accède à un de ces éléments par :
var2 = var1;
function2 = fonction1;
classe2 = classe1;
alors nom2, fonction2 et classe2 deviennent un deuxième nom par lequel on peur accéder à nom1, fonction1 et classe1..
function2 = fonction1; n'exécute pas fonction1 et classe2 = classe1; ne crée pas une objet instance de lclasse1. Pour exécuter la fonction1 et récupérer le résultat il faut faire :
res = fonction1()
Pour créer un objet instance de la classe il faut faire :
obj = new classe1; ou new classe1().. si elle a un constructeur.
Si individu est une instance d'une classe, les 2 instructions :
gens = individu ;
var gonze = Object(individu);
qui sont en fait la même, ne créent pas une nouvelle instance mais génèrent trois autres noms (comme les pointeurs du C) pour le même objet individu. Si on modifie la propriété de l’un des trois, elle est modifiée pour les trois : gens.nom = "toto" provoque également mec.nom, gonze.nom et individu.nom = "toto".
Ceci est vrai pour toutes les assignations d’objets non littéraux. Elle n’est pas vrai ans le cas des littéraux : y=2 ; s=y ; s=3 ; n’entraine pas que le littéral 2 est converti en 3, ce qui pouvait se passer à l’origine dans le cas de certains langages.
Pour créer une nouvelle instance à un objet existant, mais ayant sa propre instance en mémoire on utilise la fonction Object.create(obj) : Si on a y.marque = "Fiat" ; let z = Object.create(y); crée une copie de y, mais si on fait z.marque = "Suzuki", dans ce cas y.marque n’est pas modifié.
On a déjà vu les constantes booléennes true et false, les constantes particulières undefined, null.
Il y en a beaucoup d’autres qui mémorisent des propriétés comme les constantes entières de couleur (très nombreuses) : red, green, blue, …
Les instructions de contrôle de flux sont pratiquement les mêmes qu’en C/C++ et Java.
If (expression) {instructions exécutées si expression == true} else {instructions exécutées si expression == false} Les {} peuvent être supprimées s’il n’y a qu’une instruction à l’intérieur.
La partie else {... } peut être absente.
Une intruction peut être un nouveau bloc if.
Le bloc with prend en argument un objet qui se verra attribuer les méthodes orphelines du bloc (celles pour lesquelles aucun objet n’est spécifié). Par exemple :
with(texte)
{
if(length > 0) document.write(toUpperCase());
}
Dans le bloc les méthodes length et toUpperCase sont affcetées à l’objet texte, comme si on avait écrit : if(texte.length > 0) document.write(texte.toUpperCase());
Try { instructions essayées }
catch(erreur1_pistée) {instructions si erreur1_pistée survenue dans le bloc try}
catch(erreurN_pistée) {instructions si erreurN_pistée survenue dans le bloc try}
finally {instruction exécutées dans tous les cas}
On peut pister une ou plusieurs erreurs. Le bloc finally est facultatif while, do...while, break, continue, for while (expression) { bloc d’instructions exécutées tant que expression == true} On sort de ce bloc soit par une instruction break, soit par le fait que les instructions exécutées produisent le passage de la valeur expression à false.
Do { instructions exécutées tant que expression == true} while (expression)
Contrairement au premier while, ici l’exécution du bloc d’instruction débute même si expression == false. La sortie du bloc se fait n’importe où par un break, ou à la fin du bloc si expression == false.
For (instIni ; testFinal ; instFin) {instBoucle} L’instruction instIni est exécuté une seule fois au tout début. Il peut y en avoir plusieurs séparées par des virgules.
1.Les instructions du bloc instBoucle sont exécutées ensuite.
2.L’instruction instFin est exécutée ensuite. Il peut y en avoir plusiers séparées par des virgules.
3.Le testFinal est exécuté ensuite. Si son résultat est true une nouvelle exécution du bloc instBoucle est effectuée avec reprise au point 2. Si le résultat est false, c’est fini.
Exemple :
tab=[]
for(i=0, j=0,k=0 ; i*j < 100 ; i++, j+=2,k++) tab[k] = i*j ; => [0, 2, 8, 18, 32, 50, 72, 98]
L’exécution de l’instruction continue ; provoque, depuis son emplacement, le saut à la fin des instructions instBoucle. Le testFinal est exécuté pour voir si la boucle reprend ou non.
L’ exécution de l instruction break ; produit, depuis son emplacement, un saut à l’extérieur de la boucle.
Il existe également l'événement onError auquel on peut associer (généralement au début du code) une callback qui sera appelée si une erreur non traitée survient :
onErreur = encasdErreur;
function encasdErreur(message, url, ligne)
{
out = "Désolé, une erreur s'est produite.\n\n";
out += "Erreur : " + message + "\n";
out += "URL : " + url + "\n";
out += "Ligne : " + ligne + "\n\n";
out += "Cliquez sur OK pour continuer.\n\n";
alert(out);
return true;
}
Switch (expr) {
case valeur1 : {instructions executées si expr == valeur1; break;}
case valeurN : {instructions executées si expr == valeurN; break;}
default : {instructions executées dans les autres cas}
}
Si expr == valeurX le bloc qui suit sera exécuté, sinon il sera sauté.
S’il n’y a pas d’instruction break, à la fin du bloc d’instruction commencé, le bloc suivant sera exécuté sans examiner son case. Pour ne pas exécuter le bloc suivant, il faut terminer un bloc par l’instruction break.
Le cas default est facultatif.
Dans le switch, les comparaisons == de strings sont gérées : switch (texte)
{
case "Accueil" : document.write("Vous avez sélectionné Accueil"); break
case "À propos de" : document.write("Vous avez sélectionné À propos de"); break
}
•Le dernier break est inutile.
•On trouve parfois continue à la place de break ce qui peut laisser perplexe. En fait cela peut arriver dans le cas où le switch est à l’intérieur d’une boucle for, le continue produit alors le saut à la fin de la boucle.
For (value of iterable) { instructions à exécuter avec value prenant tout à tour toutes les valeurs de la collection iterable}.
Les principaux objets itérables sont Array, Map, Set, String, TypedArray, l’objet arguments des fonctions... L’élément value utilisé à chaque itération est la valeur d’un l’élément de l’Array, du Map, du String…
Exemple :
var a = ["y", "z", "x"];
for(v of a) console.log(v);
Résultat :
‘y’
‘z’
‘x’
Cette itération ne marche pas sur les objets ordinaires non itérables, par exemple l’objet suivant : const o = { a : "x", b : "y", c : "z"}; constitué des champs (propriétés) a, b et c, initialisés aux valeurs "x", "y" et "z" ne peut être traité par la boucle for-of : for(i of o) console.log(`${i}`);
Uncaught TypeError : o is not iterable
La méthode Object.entries(obj) génère, pour tout objet obj, un tableau de ses propriétés propres énumérables. Chaque élément du tableau est un sous-tableau [clé, valeur].
Ainsi eo = Object.entries(o) génère le tableau [[a, "x"], [b, "y"], [c, "z"]] sur lequel on peut faire un for-of :
for(w of eo) console.log(`${w[0]} = ${w[1]}`); Résultat :
a = "x"
b = "y"
c = "z"
ou plus branché :
for ([i,v] of eo) console.log(`${i} = ${v}`); Résultat :
a = "x"
b = "y"
c = "z"
Attention : L’ordre des clés est quelconque.
Boucle permettant de faire des itérations sur les propriétés énumérables des objets itérables ou non itérables ce qui pour un objet non itérable permet de se passer de la fonction Object.entries(obj). La variable obtenue à chaque itération est l’index ou la clé d’un l’élément de l’Object, Array, Map, String...(dans un ordre a priori quelconque).
Exemple :
const o = { a : 1, b : 2, c : 3 };
for(i in o) console.log(`${i} : ${o[i]}`); Résultat :
a : 1
b : 2
c : 3
Fonctionne également pour les objets itérables :
var a = [1, 5, 7];
for(i in a) console.log(`${i} : ${a[i]}`); Résultat :
0 : 1
1 : 5
2 : 7
Itérateur : Un itérateur it a une seule méthode v = it.next() qui fournit une valeur v ayant deux propriétés :
•v.done ; qui vaut true lorsque l'itération est finie et qu'il n'y a plus de valeurs (false dans l'autre cas)
•v.value : qui fournit la valeur de l'élément en cours de l'itération ou undefined quand l'itération est terminée.
Un itérateur est un objet B (d'instance it ci-dessus) associé à un autre objet A qui est itérable, comme un tableau, une liste... .A chaque appel de sa méthode next() il envoie un objet C (d'instance v ci-dessus) sous la forme :
•{done:false, value:‘valeur de l’élément’} si c’est un élément
•{done:true : value: undefined} s’il n’en reste plus.
Objet iterable : Un objet A est itérable s’il possède une méthode "[Symbol.iterator]()" (sans argument) qui renvoie un objet itérateur. C’est le cas des objets de type Array, Set et Map.
Exemple :
A = [1, 5, 7]; // un tableau qui est un objet iterable A
itB = A[Symbol.iterator](); // l’objet B qui est l’itérateur sur A
for(k=0 ;;k++)
{
objC = itB.next(); // l’objet C renvoyé
if(objC.done) break ;
console.log(objC.value)
}
1
5
7
Si on construit soit même un objet iterable, on peut adjoindre à la méthode next() de l’objet itérateur renvoyé par la méthode [Symbol.iterator](), une méthode return() dans laquelle on placera du code qui est à exécuter si l’itération est interrompue prématurement avant la fin, par exemple pour nettoyer une ressource. Cette méthode sera automatiquement appelée en cas de break dans une boucle for-of.
Remarque : Cette méthode ésotériquement appelée [Symbol.iterator]() porte également le non moins fumeux nom générique de @@iterator méthode. Bienvenue au royaume des esprits nébuleux.
L'opérateur ... déstructure un string, un tableau, un objet json en éléments (séparés par des virgules), ce qui permet d'utiliser la liste obtenue pour générer des arguments. Permet également de cloner des objets, de les concatener, etc...
Les nombres sont des objets Number sur lesquels on peut utiliser des méthodes qui aident au formattage :
Méthode .toFixed(N) : arrondi la partie décimale avec N chiffres
(1234567.987654321).toFixed(3) ⇒ 1234567.988
Méthode .split(’.’) pour séparer parties entières et décimales.
[n, d] = (1234567.987654321).toString().split(’.’); => n = 1234567 et d = 987654321
Méthode .padStart(N, ‘0’) pour mettre des 0 en tête et occuper N places : (12).toString().padStart(4, ‘0’) => 0012
ATTENTION : La méthode ‘value’ de nombreux éléments HTML (input, curseur,…) ne renvoie pas un nombre mais renvoie un string qui est généralement automatiquement convertit en nombre si on s’en sert dans une opération SAUF si cette opération est une ADDITION, auquel cas l’autre opérande est converti en string et les deux strings sont concaténés.
Il est conseillé de convertir le résultat res = element.value en nombre par une des méthodes suivantes :
n = parseInt(res,10) ; // si on attend un entier,
f = parseFloat(res) ; // si on attend un réel
f = Number(res) ; // idem, mais plus strict, car fournit isNaN(f) = true si conversion impossible v = +res ; // idem avec conversion implicite en nombre.
Pour tester si un argument est un nombre valide on fera :
let nombre = Number(argument); if (isNaN(nombre)) { ….} ou bien par :
if (typeof argument != ‘number’) {…}
typeof est plus strict car bloque l’éventuelle conversion implicite. Il peut être utilisé pour tester tous les types d’argument.
Remarque : Il y a également le type BigInt pour les entiers de très grande valeur.
Comme les strings, les tableaux sont des objets particuliers.
MyArray = new Array();
strictement équivalent à
myArray = [];
Mais aussi :
myArray = Array(1, false, "toto", [8, 45]);
ou encore :
myArray = [1, false, "toto", [8, 45]];
Taille :
myArray.length -> 4
Array.from() réalise une copie d’un objet itérable : tableau existant ou d’un objet s’apparentant au tableau comme les arguments d’une fonction, et qui peut en outre appliquer une transformation aux éléments. Le prototype est :
Array.from(arrayLike [, fonctionMap[, thisArg]]) où fonctionMap est une éventuelle fonction à appliquer à chaque élément du tableau (d’où la nécessité qu’il soit itérable) et thisArg est la valeur à attribuer à this dans cette fonction.
Exemple :
Array.from([1, 2, 3], x => x + x);
Ici le premier argument est le tableau [1, 2, 3] le deuxième argument est la fonction anonyme fléchée x => x + x. // Voir plus loin les fonctions fléchées.
Le résultat est le tableau [2, 4, 6].
Myvar1 = myArray[0]; -> 1
myVar2 = myArray[3][1]; -> 45
Les indices vont de 0 à myArray.length – 1 ;
Il n’y a pas dans javascript de tableaux associatifs comme en PHP, mais l’accès aux valeurs des propriétés des objets par le biais de [’nomPropriété’] s’y apparente beaucoup. De plus on peut rapidement créer de tels objets avec le mécanisme JSON, voir Erreur : source de la référence non trouvée.
Ajout d’éléments en queue
myArray.push(’Bradford’, ‘Brighton’);
Suppression dernier élément en queue (et acquisition) let removedItem = myArray.pop();
Ajout d’éléments en tête
myArray.unshift(’Edinburgh’);
Suppression premier élément (et acquisition)
let removedItem = myArray.shift();
ar.fill([val, [ixDep[, ixFin]]])
Remplit le tableau avec la valeur val (0 par défaut) depuis l’index ixDep (0 par défaut), jusqu’à l’index ixFin, (fin par défaut).
Arr = ["toto", "ecrit", "soda", "tete"];
arr.indexOf("soda") renvoie 2.
Renvoi -1 si pas trouvé.
La méthode some renvoie true ou false suivant qu’un élément du tableau vérifie ou non une certaine propriété. Pour faire cette vérification la méthode some prend en argument une callback qui fait le travail de vérification à partir des arguments qu’elle reçoit à savoir l’élément courant val, son index et même l’objet tableau this. La méthode some appelle cette callback successivement pour tous les éléments jusqu’à ce qu’un renvoie true, et alors elle-même renvoie true, sinon, elle renvoie false. Le prototype de la callback est le suivant :
function callback(val [, index[, tableau]])
Exemple :
// fonction callback
function gt10(val, ix, tab) { return (val > 10);} // l’index ix, et le le tableau tab ne sont pas utilisés dans ce cas. La fonction renvoie true si un élément du tableau objet est > 10.
res = [1, 5, 8, 14, 4].some(gt10);
document.write("res = " + res);
Ou plus court :
document.write("res = " + [1, 5, 81, 14, 4].some(val => val > 10)); qui renverra true et non pas le sous tableau [81, 14].
Cette méthode permet de voir s’il existe au moins un élément qui vérifie un critère donné.
Soit un Array arr, La méthode arr.slice([istart], [iend]) renvoie le sous-tableau de l’élément indice istart à l’élément indice iend-1. Si les valeurs de istart et iend sont négatives, elles sont comptées en arrière depuis la fin.
Exemples : arr = ["t", "e", "s", "t"];
arr.slice(1, 3) ==> e,s
slice(-2) ==> s,t
La méthode slice permet, en particulier, de faire une copie indépendante d'un tableau.
Arr.splice(start[, deleteCount, elem1, .., elemN])
Supprime deleteCount éléments à partir de l’élément d’indice start (qui est supprimé) et les remplace par les éventuels elem1, .., elemN.
Découpage d’un string vers tableau
let myData = ‘Manchester,London,Liverpool,Birmingham,Leeds,Carlisle’ ; let myArray = myData.split(’,’);
myArray.length ;
myArray[0]; // le premier élément du tableau myArray[1]; // le deuxième élément du tableau myArray[myArray.length-1]; // le dernier élément du tableau
La méthode split peut prendre en argument une expression régulière (rationnelle, normale, motif, regex...). Voir exemple ci-après.
Tableau vers String séparés par séparateur
let myNewString = myArray.join(’,’);
myNewString ;
Exemple : Remplacer les retours ligne window par unix : const lines = source.split(/\r\n|\n/); destination = lines.join(’\n’);
Ajoute des tableau en queue
L'opérateur "..." déstructure le tableau en une liste d'éléments séparés par des virgules, qui peut être réutilisée pour générer :
•un nouveau tableau,
•une liste d'arguments de fonction
•n'importe quoi où une liste d'éléments séparés par des virgules est attendue.
Cloner un tableau :
const arr1 = [1, 2, 3];
const cloneA = [...arr1];
Concatener 2 tableaux :
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr1Et2 = [...arr1, ...arr2];
console.log(arr1Et2); // Output: [1, 2, 3, 4, 5, 6]
Elle prend en argument une fonction qui est appliquée à chaque élément.
Function afficher(el, ix, tab) {document.write("L’élément d’indice "+ix+" vaut "+el +"<br>")}
arr = ["t", "e", "s", "t"];
arr.forEach(afficher)
La fonction appelée par forEach prend 3 arguments : l’élément, son indice et le nom du tableau qui n’est pas utilisé dans cet exemple.
Inverse l’ordre de éléments du tableau.
Sort() => trie alphabétique
sort().reverse() => trie puis renverse
sort(funtion(a,b){...return a ou b selon critère})
function(a,b){return a-b ;} : tri numérique croissant
On peut affecter un tableau à des éléments, en totalité ou partiellement;
let a = [0, 1, 2, 3];
let b = a;
let [x, , , t] = b;
x vaudra 0 et t vaudra 3
Les intructions
b = [...a];
b = a.slice();
b = Array.from(a);
réalisent tous une copie superficielle qui ne protège que les référence de premier niveau, à savoir que si a = a = [1, [2, 3]]; alors a[0] vaudra toujours 1 et a[1] sera toujours un tableau à 2 éléments mais leurs valeurs ne sont pas protégées, ainsi b[1][0] = 4; écrase a[1][0] qui de 2 prend la valeur 4.
Pour faire une copie profonde il y a la méthode :
b = structuredClone(a);
qui permet de tout cloner même les types complexes (Date, Map, Set, ArrayBuffer, etc.)
Renvoie un tableau réalisé en appliquant la callback argument aux éléments du tableau. Exemple :
function carre(x) {return x*x};
aa=new Array(3,4,5).map(carre);
Renverra : [9, 16, 25]
Autre exemple :
<!DOCTYPE html>
<html><head><meta charset="UTF-8"></head>
<body><script>
const Stars = [
[ 0, "Achernar" , 1.62858, -57.23667],
[ 1, "Agena" , 14.06372, -60.37306],
[ 1, "Aldebaran" , 4.59867, 16.50917],
[ 3, "Altair" , 19.84639, 8.86833],
[ 4, "Antares" , 16.49011, -26.43194],
[ 5, "Arcturus" , 14.26103, 19.18250],
[ 6, "Betelgeuse", 5.91953, 7.40694],
[ 7, "Canopus" , 6.39919, -52.69583],
[ 8, "Capella" , 5.27817, 45.99806],
[ 9, "Procyon" , 7.65503, 5.22500],
[10, "Rigel" , 5.24231, -8.20167],
[11, "Spica" , 13.41989, -11.16139],
[12, "Toliman" , 14.65997, -60.83528],
[13, "Vega" , 18.61564, 38.78361]];
const noStars = [2, 8, 12];
let selStars = noStars.map(i => Stars[i][1]);
p = document.createElement('p'); document.body.append(p);
p.textContent = selStars.toString();
</script></body></html>
Ici on crée le tableau selStars à partir du tableau noStars qui sert d'index de ligne dans le tableau Stars dans lequel on applique la fonction i => Stars[i][1] qui sélectionne la colonne 1 de toutes les lignes.
Pour extraire le vecteur colonne des index : const ids = Stars.map(star => star[0]);
et celui des noms : const noms = Stars.map(star => star[1]);
Les strings sont, au niveau des méthodes, pratiquement considérés comme des tableaux de caractères. A l’exception des méthodes de construction d’Array, les méthodes qui s’appliquent aux tableaux s’appliquent aux strings.
Ils sont proches des strings du C++ et java, initialisés entre " et ", entre ‘ et ‘ mais aussi entre ` et `.
const string1 = "Une chaîne de caractères primitive" ;
const string2 = 'Une valeur de chaîne de caractères primitive' ;
const string3 = `Et ici aussi`;
const string4 = new String("Un objet String");
Les guillements graves ` sont appelés des littéraux de gabarit. Ils permettent de conserver les retours à la ligne et peuvent incorporer des variables ou des expressions entre accolades précédées d’un $ comme ceci ${myVar}, ${Expression}, ${3+4},...dont la valeur chaine sera concaténée pour générer la chaine résultat.
Exemple :
console.log(`La somme ${3} + ${4} vaut ${3+4}`); => La somme 3 + 4 vaut 7
Mais avec des () à la place des {}, cela reste des caractères ordinaires.
Console.log(`La somme $(3) + $(4) vaut $(3+4)`); => La somme $(3) + $(4) vaut $(3+4)
Les littéraux de gabarits sont très utilisés pour incorporer du code html dans un string, comme ici :
container.innerHTML = `
<textarea id="codeInput" rows="4" cols="50" placeholder="Écrivez ici.">
</textarea><br><button onclick="executerCode()">Exécuter</button> `;
Return 'chat'.charAt(2); // renvoie "a"
return 'chat'[2]; // renvoie "a"
Remarquons que la chaine primitive est automatiquement convertie en objet String pour lui offrir l’accès aux fonctions prenant des strings. Mais ce n’est pas le cas pour les fonctions typeof et eval qui ne modifient pas le type de leur argument.
Let s_prim = "toto" ;
let s_obj = new String(s_prim);
console.log(typeof s_prim); // affiche "string"
console.log(typeof s_obj); // affiche "object"
let s1 = "2 + 2" ; // crée une chaîne primitive
let s2 = new String("2 + 2"); // crée un objet String console.log(eval(s1)); // renvoie le nombre 4
console.log(eval(s2)); // renvoie la chaîne "2 + 2"
Un objet String peut toujours être converti en son équivalent primitif grâce à la méthode valueOf().
Console.log(eval(s2.valueOf())); // renvoie 4
Stra + strb
StrA || strB donne strA si non vide, sinon donne strB Chaines longues
Soit de manière ordinaire avec l’opérateur + :
let chaineLongue = "Voici une très longue chaîne qui a besoin " +
" d’être passée à la ligne parce que sinon " +
" ça risque de devenir illisible." ;
Soit avec anti-slash + Return
let chaineLongue = "Voici une très longue chaîne qui a besoin \
d’être passée à la ligne parce que sinon \
ça risque de devenir illisible." ;
Délimitée par accent grave pour conserver les Return let chaineLongue = `Voici une très longue chaîne qui a besoin d’être passée à la ligne parce que sinon
ça risque de devenir illisible.`;
str.substring(ixdeb, ixfinp1) : renvoie la sous chaine de str commençant à l’index (base 0) ixdeb, jusqu’à l’index ixfinp1-1, ainsi avec s = ‘0123456789’, s.substring(2,5) renvoie ‘234’. Le nombre de caractères renvoyés est égal à ixfinp1-ixdeb.
Déstructure l'array en ses éléments
const str = "Hello";
const charArray = [...str];
console.log(charArray); // Output: ['H', 'e', 'l', 'l', 'o']
Pas encore citées ? : at , codePointAt, concat, includes, endsWith,indexOf, lastIndexOf, localeCompare, matchAll, normalize, padEnd, repeat, replaceAll, search, slice, split, startsWith, substring, toLocaleLowerCase, toLocaleUpperCase, toLowerCase, toUpperCase, trim, trimStart, trimEnd, valueOf…
Les expressions régulières, également appelés motifs, regex ou expressions rationnelles ou expressions normales sont des chaines de caractères qui prennent une signification particulière.
Il y a 3 manières de définir une instance d'expression rationnelle :
1) /modèle/marqueurs : Exemple : const regex1 = /\w+/; pour définir une chaine constitué de 1 ou plusieurs lettres de mots (/w == [a-zA-Z_0-9]). L'expression est délimitée par les deux barres obliques.
2) new RegExp(modèle[, marqueurs]) : Exemple : const regex2 = new RegExp('\\w+'); Ici l'expression est délimitée par les apastrophes et le \ de \w doit être échappé.
3) RegExp(modèle[, marqueurs]) : Exemple : const regex2 = RegExp('\\w+'); Idem, le mot new en 2) étant facultatif.
La méthode regExp.test(chaine) renvoie true ou false suivant qu'une correspondance avec l'expression régulière est trouvée ou non dans la chaine.
Remarque : Dans les fichiers .htaccess, les expressions régulières sont directement utilisées sans délimiteurs.
Elément |
Signification |
Exemple |
/ |
Début et fin de l'expression rationnelle |
/hello/ sans guillemets autour !!! |
. |
Correspond à tout caractère unique saufnne ligne |
c.t correspondra à cat, cot, cut, etc. |
+ |
Répète le caractère de correspondance précédent une ou plusieurs fois |
a+ correspond à a, aa, aaa, etc. |
* |
Répète le caractère de correspondance précédent zéro ou plusieurs fois |
a* correspond à tout ce à quoi correspond a+, mais correspond aussi à la chaîne vide. |
? |
Rend la correspondance optionnelle (0 ou 1 fois) |
colou?r correspondra à color et colour. |
\ |
Echappe le caractère suivant |
\. correspondra à . (le point) et non à tout caractère unique comme expliqué plus haut |
^ |
correspondance au début de la chaîne |
^a correspond à une chaîne qui commence par a |
$ |
correspondance à la fin de la chaîne. |
a$ correspond à une chaîne qui se termine par a. |
( ) |
Regroupe plusieurs caractères en une seule entité, et conserve une correspondance à des fins d'utilisation dans une référence arrière. |
(ab)+ correspond à ababab - à savoir, le + s'applique au groupe. Pour plus de détails sur les références arrières, voir ci-dessous. |
[ ] |
Un caractère parmi l'ensemble |
c[uoa]t correspond à cut, cot ou cat. |
[p-t] |
Un caractère parmi l'ensemble p q r s t |
|
[^ ] |
Un caractère qui n'est pas dans l'ensemble |
c[^/]t correspond à cat ou c=t mais pas à c/t |
ab|yz |
Identifie ab et/ou yz |
|
\b |
Identifie une limite de mot |
|
\B |
Identifie une absence de limite de mot |
|
\d |
Identifie un seul chiffre |
|
\D |
Identifie un caractère non chiffre |
|
\n |
Identifie un caractère nlle ligne |
|
\r |
Identifie un caractère retour début de ligne |
|
\s |
Identifie un caractère d'espacement |
|
\S |
Identifie un caractère non espacement |
|
\t |
Identifie un caractère tabulation |
|
\w |
Identifie un caractère de mot [a-zA-Z0-9_] |
|
\W |
Identifie un caractère pas de mot |
|
\x |
Identifie le caractère x |
|
{n} |
Identifie exactement n occurences |
|
{n,} |
Identifie n occurences ou plus |
|
{min,max} |
Identifie entre min et max occurences |
|
Attention : Les caractères accentués àéè... ne sont pas inclus dans \w. Il faut les ajouter et définir la RegExp /a-zA-Z0-9_àâçèéêëîôùûüÂÀÉÊÈÔÛÇ/.
Après la RegExp on peut ajouter un délimiteur final :
g |
global cad toutes les occurences trouvées et non pas uniquement la première |
i |
indépendant de la casse (minuscules ou majuscules) |
m |
recherche multiligne |
u |
utilisation d'unicode |
y |
adhérence (sticky ???) |
Exemples : Trouver tous les nombres dans paragraph = ‘er678ty12uuu’ ; paragraph.match(/[0-9]+/g); ==> Array ["678", "12"]
Remplacer les espaces ordinaires par des espaces html insécables ‘  ;’ : String.replace(/ /g, ‘  ;’);
Effacer les espaces en fin de lignes (simuler trim) : String.replace(/^\s+|\s+$/g,’’);
Remplacer les retour ligne window par unix : const lines = source.split(/\r\n|\n/); destination = lines.join(’\n’);
La méthode regExp.test(chaine) renvoie true ou false suivant qu’une correspondance avec l’expression régulière est trouvée ou non dans la chaine.
Autre exemple :
str = "For more information, see Chapter 3.4.5.1" ;
re = /e (chapter \d+(\.\d)*)/i ;
trouve = str.match(re);
console.log(trouve);
console.log(trouve.index);
console.log(trouve.input);
console.log(trouve.groups)
Renvoie :
Array(3) [ "e Chapter 3.4.5.1", "Chapter 3.4.5.1", ".1" ]
24
For more information, see Chapter 3.4.5.1
undefined
Explications :
trouve.length vaut 3 :
trouve[0] = ‘e Chapter 3.4.5.1’ qui est le string solution
trouve[1] = ‘Chapter 3.4.5.1’ qui correspond au 1er groupe (chapter \d+(\.\d)*) trouve[2] = ‘.1’ qui correspond au dernier groupe (\.\d)
puis :
trouve.index = 24, qui est l’index du 1er caractère de la correspondance
trouve.input = le string source initial
trouve.groups : indéfini.
Contrairement au C++ et PHP javascript ne possède pas les conversions explicite de type (int).., (float)... On utilise les fonctions spécialisées suivantes :
Fonction |
convertit vers |
parseInt() |
Int, Integer |
Boolean() |
Boll, Boolean |
parseFloat() |
Float, Double, Real |
String() |
String |
split() |
Array |
La déclaration traditionnelle d’une fonction consiste à faire suivre le mot clé function du nom de la fonction, puis d’une paire de parenthèses () avec d’éventuels arguments de la fonction, puis d’une paire d’accolades {} avec les instructions de la fonction. Exemple :
function carre(n) {return n*n ;}
document.write(carre(5));
Il y a 2 manières simples de renvoyer plusieurs arguments : Renvoyer un tableau qui les contient, comme ceci :
function calArr(a, b) {
return [a + b, a – b, a * b];
}
où bien renvoyer un objet créé pour la circonstance.
Function calObj(a, b) {
s = a+b ; d = a-b ; p = a*b ;
return {somme : s, difference : d, produit : p};
}
Considérons la déclaration traditionnelle de fonction suivante :
function addition(a, b) {return a+b ;}
Nous avons vu qu'on pouvait affecter le nom d'une fonction comme ceci :
var somme = addition;
ce qui permet ensuite de faire
res = somme(3,4);
Mais on peut aussi réaliser l'affection au moment de la déclaration :
var somme = function addition(a, b) {return a+b ;}
Mais on peut aussi ne pas donner de nom à la fonction et affecter cette fonction anomnyme :
var somme = function(a,b) {return a+b ;} // somme(2,3) ==> 5
On peut également écrire une fonction sans la nommer (ou en la nommant) et l'exécuter en séquence :
function(a,b) {return a+b ;}(2,3);
Dans le cas de la fonction anonyme, on peut déplacer le mot-clé function, et le remplacer par => après les parenthèses contenant les éventuels arguments :
2) var somme = (a,b) => {return a+b ;} // somme(2,3) ==> 5
et même comme ceci lorsqu'il n'y a qu'une instruction return en l'omettant et en supprimant les accolades :
3) var somme = (a,b) => a+b ; // somme(2,3) ==> 5
mais PAS comme ceci :
4) var somme = (a,b) => {a+b ;} // somme(2,3) ==> undefined
La méthode 4 NE FONCTIONNE PAS.
Bien que très utilisée par les nouveaux programmeurs, je déconseille aussi d’utiliser la méthode 3, qui ressemble trop à la méthode 4, car on peut être tenté d’y mettre les { } et faire une méthode qui ne marche pas.
Pour les méthodes 2 et 3 s’il n’y a qu’un seul argument, on peut omettre la paire de parenthèse : 3-bis) var carre = a => a*a ; // carre(3) ==> 9 par contre s’il, n’y a aucun argument, les parenthèses sont nécessaires : 3-ter) var deuxPi = () => Math.PI*2 ; // deuxPi() ==> 6.28
Ces fonctions fléchées sont intéressantes à utiliser quand elle n’interviennent qu’une fois et que leur code est simple, mais leur code est difficile à lire.
Remarque : Renvoi d'un objet JSON.
Les objet JSON de type {cle1:v1, cle2:v2} sont décrits plus loin. Si la fonction => renvoie un tel objet, par exemple {add:a+b}, on ne peut pas utiliser var somme = (a,b) => {add:a+b}; car
il y a ambiguïté entre accolades de définition du corps de fonction et accolades de définition du corps de l'objet JSON. On fera : var somme = (a,b) => ({add:a+b}); car là il n'y a qu'un terme qui est entre les () c'est donc un retour de l'objet JSON.
ATTENTION : Dans une fonction fléchée, le mot clé this ne fait pas référence à l’objet dans le corps duquel elle est définie mais à son parent ou au contexte global (window) quand il n’en a pas. Cela peut être très perturbant avec un this qui ne fonctionne pas comme on l’attend. En conséquence, si on emploie this dans la fonction, je préconise de ne pas utiliser () => mais function().
Les IIFE sont des fonctions qui sont exécutées immédiatement à l'emplacement où elles sont codées. Elles constituent à la fois la définition de la fonction et son appel. Elles sont anonymes puisqu'elles n'ont pas besoin de nom. Elles sont généralement (mais pas que) utilisées pour des initialisations.
Une IIFE sans argument est définie comme suit :
(function() {
// Code ici
})();
Une IIFE avec 2 arguments a et b est définie comme suit :
(function(a, b) {
console.log(a + b); // Affiche 3
})(1, 2);
On peut même utiliser une forme fléchée :
(() => {
console.log("IIFE avec une flèche");
})();
On peut affecter une valeur par défaut à des arguments dans la liste des arguments.
function incremente(x, i = 1) {
return x + i;
}
let y1 = incremente(5); // Utilise i=1 par défaut → retourne 6
let y2 = incremente(5, 3); // Utilise i=3 → retourne 8
let y3 = incremente(5, 0); // Utilise i=0 → retourne 5
Remarques :
•Les paramètres avec valeur par défaut doivent être placés après les paramètres obligatoires.
•La valeur par défaut est calculée au moment de l'appel.
•On peut mettre une valeur par défaut en utilisant des arguments précédemment définis dans la liste des arguments
•On peut mettre une valeur par défaut d'expression complexe.
•Dans l'appel on peut peut utiliser undefined (la valeur par défaut sera prise)
•Dans l'appel on peut peut utiliser null (la valeur null sera prise)
Considérons la fonction :
Function calObj(a, b) {
s = a+b ; d = a-b ; p = a*b ;
return {somme : s, difference : d, produit : p};
}
A l'intérieur de la fonction calObj (et uniquement à l'intérieur), arguments.length vaut 2, arguments[0] est un alias pour a et arguments[1] est un alias pour b.
On peut même modifier la valeur d'un argument en lui donnant une valeur : arguments[1]= ...
Attention : arguments n'est pas un vrai tableau, mais il peut être converti en vrai tableau. Exemples :
var args = Array.prototype.slice.call(arguments);
pour tout le tableau ou bien :
var args = Array.prototype.slice.call(arguments, 1);
en sautant l'argument 0, on commence au 2ème (index 1 pour start) et jusqu'au dernier puisque end n'est pas précisé.
Dans ces instructions la méthode slice qui est normalement accolée à une instance de tableau est appelée en tant que méthode statique de l'objet Array, d'où la syntaxe Array.prototype.slice, pour ensuite faire un appel (call) avec le tableau en premier argument suivi des arguments de la méthode slice.
Autre méthode pour convertir les arguments en tableau :
tab = Array.from(arguments);
ou encore
tab = [...arguments];
Ce pseudo tableau arguments est très utile pour faire des fonctions à nombre variable d'arguments.
Exemple :
function somme()
{
var res = 0;
if(arguments.length < 1) return res;
for (i in arguments) res += arguments[i];
return res;
}
console.log(somme(1,2,3,4,5)); // 15
Autre Exemple :
function addition()
{
var res = 0;
tab = Array.from(arguments);
for (v of tab) res += v;
return res;
}
console.log(addition(6,7)); // 13
Autre Exemple :
function ajoute()
{
var res = 0;
tab = [...arguments];
for (v of tab) res += v;
return res;
}
console.log(ajoute(3,4,5)); // 12
Personnellement je ne les utilise pas, mais on peut les rencontrer dans des programmes écrits par des intégristes de la programmation objet ou par des novice qui croient bien faire.
Le mot clé get associé à la définition d’une fonction sans argument, par exemple vaut(){return "xyz" ;} permettra de l’appeler par son nom sans parentèses (alors qu’il en faut normalement dans le cas d’un attribut de type fonction) comme dans toto.vaut pour obtenir la valeur de retour de cette fonction. Elle est généralement utilisée pour obtenir la valeur principale de l’objet, d’où le nom get.
Le mot clé set associé à la définition d’une fonction avec un argument newval(arg){...} permettra de l’appeler par son nom avec l’attribut sans parentèses et de fournir l’argument après un signe "=" comme dans instance.newval = "abc". Elle est généralement utilisée pour affecter une valeur principale fréquemment modofiée de l’objet d"ou son nom set.
Exemple :
let titi = {
variete :"oiseau",
get genre() {return this.variete ;},
set modifie(x){this.variete=x ;}
};
titi.genre ==> oiseau
titi.modifie = "cheval" ; titi.genre ==> "cheval".
Les getters et setters sont utilisés en java, C++... ou le mot clé private est utilisé pour protéger certaines données et fonctions. En javascript, qui est beaucoup plus permissif, on peut tout simplement accéder à titi.variete ==> "oiseau" ou le modifier par titi.variete = "cheval", et si on veut fournir une fonction pour le lire et l’écrire on peut faire comme ceci :
let toto = {
variete :"oiseau",
genre :function(){return this.variete ;},
modifie :function(x) {this.variete=x ;}
};
et accéder au genre par :
toto.genre() ==> "oiseau"
et le modifier par
toto.modifie("cheval"); ==> toto.variete = "cheval".
Considérons quelques fonctions ordinaires :
function m(a,b) { return a * b ;}
function a(a,b) { return a + b ;}
Une callback est une fonction dont on ne connaît pas le contenu , que l’on doit exécuter quelque part. L’utilisateur fournit simplement le nom de la fonction à exécuter, généralement en argument d’une autre fonction, ou en l’assignant à une variable dédiée à la mémoriser.
Exemple 1 :
function calcul(funcInconnue, num1,num2) { result = funcInconnue(num1, num2);
return result ;
}
que l’on peut invoquer comme ceci : calcul(m, 3, 4) qui renverra 12 ou calcul(a, 3, 4) qui renverra 7.
Exemple 2 :
<html>
<script>
function multiply(a,b) {return a*b ;}
function ajoute(a,b) {return a+b ;}
function calcule(finc, a, b) {return finc(a,b);} document.write(calcule(ajoute, 6, 7));
document.write("<br/>"+ calcule(multiply, 6, 7)); document.write("<br/>"+ calcule((a,b)=>{return a-b}, 8, 3)); </script>
</html>
Donne en résultat
13 42 5
Dans le dernier document.write au lieu de passer en argument le nom d’une fonction comme multiply ou ajoute, on a mis la fonction anonyme (a,b)=>{return a-b}.
La syntaxe de javascript est extrêmement permissive au niveau de l’écriture des fonctions de rappel Ainsi une fonction de rappel dont le prototype prévoit 2 arguments en général, comme quelconque(a,b) peut être définie comme uncas(a) avec seulement le 1er argument si on n’a pas besoin du second, et même comme autrecas() si on n’a besoin d’aucun des deux.
Un générateur est une fonction particulière qui peut être interrompue et reprise... souvent utilisée pour des fonctions asynchrones. Elle renvoie un itérateur dont la méthode next() permet de l'activer ou de la récativer. Le mot clé function est remplace par function* et return est remplacé par yield.
Comme elle renvoie un itérateur, l'objet peut être utilisé dans un for-of.
Exemple : calculer les carrées d'un tableau de nombres
Code classique ordinaire :
let nombres = [1,2,3,4,5];
function squares(tab)
{
let r = [];
for(let n of tab) r.push(n*n);
return r;
}
document.body.append(document.createTextNode(squares(nombres).toString()));
Résultat : [1, 4, 9, 16, 25]
Code avec un générateur.
function* carres(tab) {
for(let n of tab) yield n*n;
}
let nombres = [1,2,3,4,5];
function calcul(tab)
{
let itc = carres(tab);
let r = [];
for(let k = 0;;k++)
{
let v = itc.next();
if(v.done) break;
r.push(v.value);
}
return r;
}
let res = calcul(nombres);
document.body.append(document.createTextNode(res.toString()));
Delete a[3]; // détruit l’objet d’index 3 du tableau a. Il n’est plus accessible, mais les autres restent en place et le nombre d’élément du tableau n’est pas changé.
Ne peut pas supprimer des variables déclarées avec varou les variables prédéfinies.
Typeof toto ; // renvoie function, string, number, object,... suivant le cas.
Typeof(toto); //idem
Propriete in object ; // Renvoie true ou false suivant que propriete appartient à l’objet.
Var arbres = new Array("sequoia", "laurier", "cèdre", "chêne", "érable"); 0 in arbres ; // renvoie true
3 in arbres ; // renvoie true
5 in arbres ; // renvoie false
"laurier" in arbres ; // renvoie false (l’opérateur se base sur l’indice et pas // sur la valeur)
"length" in arbres ; // renvoie true (length est une propriété d’un objet Array) ... (opérateur de décomposition) Pour un objet itérable (Array, String,...) par exemple mytab=[1, 2, 3], l’opérateur ...mytab génére la séquence 1, 2, 3 des éléments ou propriétés séparés par des virgules. Cela peut être utilisé pour passer des arguments à une fonction, ou des éléments à un tableau, par exemple : tab2 = [0, ...tab, 4];
est équivalent à tab2 = [0,1,2,3,4].
var now = new Date(); // Fourni une structure date actuelle qui contient : now.getDay() : numéro du jour de la semaine : 0→Dimanche, 6→Samedi now.getDate() : jour du mois de 1 à 31
now.getMonth() : numéro du mois : 0 à 11 (ATTENTION) now.getFullYear() : numéro de l’année
now.getHours()
now.getMinutes()
now.getSeconds()
// Conversion Date en string
function strDate(d) { // Renvoie le string "jj mois année" de Date d.
let str = lj[d.getDay()] + " " + d.getDate(); if (d.getDate() == 1) str += "er"
str += " " + lm[d.getMonth()] + " " + d.getFullYear(); return str ;
}
// Conversion date en H-M-S
function strHeure(d) { // Renvoie le string "HhhMMmSSs" de Date d.
let h = String(d.getHours()).padStart(2, ‘0’); let m = String(d.getMinutes()).padStart(2, ‘0’); let s = String(d.getSeconds()).padStart(2, ‘0’); return `${h}h${m}m${s}s`;
}
• L’objet Date n’est ni local, ni UTC, il est absolu. Pour réaliser cela il est en fait convertie en interne en millisecondes écoulées depuis le Thu, 01 Jan 1970 00 :00 :00 GMT, valeur qui est accessible par date.getTime() ;
• Les méthodes standards permettent de manipuler la version locale de cette date et il existe des méthodes adaptées, avec les caractères UTC dans leur nom qui permettent de manipuler la version UTC.
- Quand on crée date = new Date(y, m-1, d, h, mn, ss) on crée une date où h est interprété comme l’heure locale h. Ainsi date.toString() fournira y/m/d h :ms :ss GMT+delta avec une valeur de delta qui indique que cette date est en avance de delta heures sur l’heure GMT et la méthode date.toUTCString() fournira y/m/d h-delta :ms :ss GMT sans delta, c’est-à-dire la version UTC de la date.
Date.getTimezoneOffset() fournit la valeur -delta*60, c’est-a-dire le nombre de minutes qu’il faut retrancher à la version locale pour obtenir la version UTC. L’abscisse du zéro qui se trouve à -delta*60 minutes par rapport au local ? Si delta est > 0 le temps local est en avance, il faut retrancher cet offset pour trouver le temps par rapport à l’origine. Les pays à l’Est de Greenwich (longitude positive) sont en avance avec delta > 0. Il est par exemple 14h à Berlin quand il est 12h à Greenwich.
Les accès par getHours(), getMinutes()… fournissent les éléments de la version locale et les accès par getUTCHours(), getUTCMinutes()… fournissent les éléments de la version UTC de la même date.
Si delta est un multiple exact d’heures, on peut vérifier que delta = date.getHours() - date.getUTCHours() et date.getTimezoneOffset() = -delta*60.
L’opérateur – (moins) est surchargé (mais pas l’opérateur +) pour permettre de retrancher deux dates (ou un nombre de ms à une date) et ainsi obtenir une durée écoulée entre deux dates en millisecondes.
duree_ms = date1 – date2 ;
Pour ajouter une durée à une date on fera date.setTime(date.getTime() + duree_ms) ;
La méthode statique Date.UTC(y,m-1,d,h,mn,s) fournit le nombre de ms écoulées depuis la date UTC y/m/d h :ms :ss GMT . Ainsi new Date(Date.UTC(y,m-1,d,h,mn,s)) permet de créér la date correspondant à cet événement, alors que new Date(y, m-1, d, h, mn, ss) crée une date qui va en différer de getTimezoneOffset().
La méthode .toLocaleString() fournit la date sous la forme 26/04/2025 19:19:32 .
L’objet window offre des fonctions très utiles pour communiquer avec l’utilisateur.
En introduction, nous avons déjà vu sa méthode prompt avec l’instruction :
x = window.prompt("Donner un nombre ", 0);
Les fonctions de l’objet window sont si utiles qu’il est inutile de spécifier le nom de l’objet, il suffit de mettre le nom de la fonction, et par défaut JavaScript suppose qu’une fonction orpheline concerne l’objet window :
x = prompt("Donner un nombre ", 0);
Il en va de même avec les fonctions suivantes :
alert(texte) : Pour afficher un texte dans une fenêtre PopUp.
Brep = confirm(texte) : Pour afficher une question dans une fenêtre PopUp qui attend une réponse. Retournera true ou false.
print() : Pour envoyer la page à l’imprimante.
stop() : Pour arrêter le programme.
Il est possible en javascript de programmer une action différée avec la méthode setTimeout : var action = setTimeout(frappel, delai_ms [, args]);
La callback frappel([args]) sera exécutée après un delai de delai_ms millisecondes. Les éventuels arguments args de la callback sont placés après l’argument delai_ms.
En mettant delai = 0, la callback sera exécuté sans délai, mais après que tout le code du script ait été exécuté, ce qui utile pour exécuter du code après que toutes les initialisations aient été effectuées.
Il est possible avec cette fonction de réaliser des actions répétitives. Il suffit pour cela que la callback place l’instruction setTimeout(frappel, delai_ms); à la fin des instructions de son travail, comme cela ces instructions sseront excéutées à nouveau après l’écoulement du délai et ceci indifiniment. Mais une fonction plus approprié existe, c’est la fonction setInterval.
La fonction setInterval permet de programmer des actions répétitives séparées par un intervalle de temps donné en millisecondes :
var action = setInterval(fonction, delai_ms [, args]); La fonction fonction sera exécutée répétitivement avec un intervalle de delai_ms millisecondes.
L’intérêt par rapport à setTimeout qui utilise un callback qui rappelle setTimeout pour réaliser la répétition est, à part la simplicité apportée, que la première exécution de la callback est immédiate alors qu’avec setTimeout elle ne se fait qu’après le delai_ms.
On peut simuler le fonctionnement de setInterval avec setTimeout. Exemple :
let i = 1;
setInterval(()=>{console.log(i); i++; }, 1000);
qui produira une impression log toutes les secondes. Elle peut être réalisée également par :
let i = 1;
setTimeout(function run() {console.log(i); i++; setTimeout(run, 1000);}, 1000);
où la fonction setTimeout se rappelle elle-même à la fin de son exécution.
Dans ce deuxième cas l'intervalle sera légèrement plus long car il comprendra en plus le temps d'exécution de la callback (que nous avons du nommer pour la citer dans le deuxième appel).
Ces deux fonctions sont équivalentes. Elles permettent la suppression d’une fonction différée ou répétitive : La variable action retournée par setTimeout et/ou setInterval peut servir à supprimer le lancement de la fonction différée en appelant clearTimeout(action); avant son exécution (qui se fait après l’attente du delai_ms) ou si clearInterval(action); est appelé avant ou en cours des actions répétitives suivantes.
Remarques :
•Dans le cas où la callback n’a pas d’arguments, si l’argument delai_ms est également absent il est pris égal à 0.
•Les fonctions clearTimeout et clearInterval sont interchangeables.
La fonction requestAnimationFrame est utilisée pour rafraichir des tracés graphiques. Elle est optimisée pour que le rendu soit le plus fluide possible, tout en minimisant la charge de calcul. Son fonctionnement s'apparente à la simultation de setInterval par l'appel récursif de setTimeout .
La fonction requestAnimationFrame ne prend que la callback a exécuter en argument, car le délai est fixé par le rafraichissement optimal de l'écran (60, 50 ou 30 Hz suivant la carte graphique). Exemple :
function dessine() {
... Dessin de la scene ....
// Rappel de la fonction de dessin par le requestAnimationFrame
idRaf = requestAnimationFrame(dessine);
}
// Premier appel de la fonction de dessin
dessine();
Pour une carte graphique rafraichissant à 60 Hz (période de 17 ms) le code équivalent avec setInterval serait :
function dessine() { // ici code du dessin}
idSi = setInterval(dessine, 17);
La callback dessine peut utiliser un argument facultatif : un timestamp qui est l'intervalle de temps écoulé en millisecondes depuis le chargement de la page. Attention, il se peut qu'il soit !isFinite à la première itération.
Pour démarrer ou arrêter l'animation par un clic sur le body du document peut utiliser le code suivant, à la place de l'appel initial dessine();
let anime = false;
document.body.addEventListener('click', () => {
if (spinning) {cancelAnimationFrame(idRaf); anime = false;}
else { dessine(); anime = true;}
});
On charge une nouvelle page en spécifiant une url à document.location ou window.location. C'est la même chose, mais window.location est préconisé par Microsoft et document.location est préconisé par Mozilla.
En lecture location fournit l'url de la page en cours.
alert(location); => https://llibre.fr
En assignation, il génère le chargement de l'url fournie :
location = 'http://www.example.com'
est synonyme de
location.href = 'http://www.example.com'
et de
location.assign('http://www.example.com')
et de
location.replace('http://www.example.com');
mais dans ce dernier cas le retour arrière n'est pas possible car la page actuelle n'est pas sauvegardée dans l'historique.
Rechargement de la page en cours :
location.reload([false]); // Recharge la page éventuellement depuis le cache
location.reload(true); // Recharge la page depuis le serveur
Signification des attributs : Exemple avec location : https://developer.mozilla.org:8080/en-US/search?q=URL#search-results-close-container
var loc = document.location; // Pour être plus concis
console.log(loc.href); // https://developer.mozilla.org:8080/en-US/search?q=URL#search-results-close-container
console.log(loc.protocol); // https:
console.log(loc.host); // developer.mozilla.org:8080
console.log(loc.hostname); // developer.mozilla.org
console.log(loc.port); // 8080
console.log(loc.pathname); // /en-US/search
console.log(loc.search); // ?q=URL
console.log(loc.hash); // #search-results-close-container
console.log(loc.origin); // https://developer.mozilla.org:8080
DOMContentLoaded est un événement qui est déclenché quand le document HTML initial est complètement chargé et analysé, sans attendre la fin du chargement des feuilles de styles, images et sous-document. Exemple simple d'utilisation :
document.addEventListener("DOMContentLoaded", (event) => {
console.log("DOM fully loaded and parsed");
});
Mais on peut également l'utiliser pour exécuter un script, à la fin de ce chargement et éventuellement mettre tout le script à exécuter pour générer la page à l'intérieur de la callback (voir plus loin document.addEventListener("DOMContentLoaded", callback);
eval(str) : évalue le résultat javascript de la chaine str.
isFinite(val) : false si val (éventuellement convertie en nombre) est un nombre fini.
isNaN(val) : true si val ne peut être converti en nombre
parseFloat(str) : Convertit str en nombre float
parseInt(str,base) : Convertit str en nombre entier selon la base base.
decodeURI(uri) : Convertit l'uri en caractères locaux (utf-8 à priori)
encodeURI(uri) : Convertit les carcatères locaux de l'uri en séquences %XX%YY...
decodeURIComponent(euri) : Idem decodeURI(uri) pour une partie de l'uri
encodeURIComponent(luri) : Idem encodeURI(uri) pour une partie de l'uri
La fonction eval est particulièrement puissante et peut servir pour réaliser un interpréteur.
Exemple :
<!DOCTYPE html><html lang="fr"><head><meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"></head>
<body>
<!-- Textarea conversationnel et bouton d'exécution -->
<textarea id="codeInput" rows="4" cols="50" placeholder="Instructions JS ici..."></textarea>
<br><button onclick="interpreterCode()">Exécuter</button>
<script>
// Une fonction qcq que l'on appellera dans l'interptréteur
function somme(a, b) {return a + b;}
// La callback appelée par un click sur le bouton "Exécuter"
function interpreterCode() {
const input = document.getElementById("codeInput");
try {
const result = eval(input.value); // Interprétation du contenu !!!!!!!
input.value += "\n=> " + result; // Ajout du résultat à la suite
} catch (error) {input.value += "\nErreur: " + error;}
}
</script></body></html>
Le format de desciption de données JSON (JavaScript Object Notation) est plus simple que le format XML : Les données sont présentées sous forme de paire cle : valeur, ou la cle est un identifiant quelconque choisi par l’utilisateur pour répérer la valeur. Les différents paires sont séparées par des virgules "," et l’ensemble des paires est enserré entre deux accolages { les paires }.
Ce format est utilisé en javascript comme constructeur d’objets de la manière suivante :
var individu = {
nom : ‘Martin’,
age : 32,
sexe : ‘masculin’,
salutation : function()
{
alert("Bonjour ! Je m’appelle " + this.nom + ".");
}
};
•La collection complète des membres est enserrée entre une paire d’accolades { }.
•Le nom de chaque membre (la clé) est suivi d’un " :" puis de sa valeur, puis d’une virgule ",", sauf pour le dernier membre.
•Dans une fonction membre le mot clé this permet d’accéder l’objet : this.nom est dans ce cas équivalent à individu.nom.
La valeur peut être un objet simple, ou complexe : une fonction, un nouvel objet JSON…
Dans le cas des clés ordinaires, on accède à un membre au moyen de sa clé, soit à l'aide de la notation point "." suivie du nom du membre, soit à l'aide de la notation crochet [..] avec le nom de la clé, entre guillemets, à l'intérieur. Ces deux notations sont équivalentes :
personne.age = 12; ou personne['age'] = 12;
personne.non.prenom = 'Michel'; ou personne['nom']['prenom'] = 'Michel';
Lorqu'une clé est un nombre, on ne peut pas accéder à la valeur avec la notation ".". Exemeple :
let obj = {cle_1 : "un", 3:"ceux"};
On accède à la clé 3 uniquement par obj[3] qui fournit "deux', mais obj.3 ne marche pas.
La fonction Symbol(string) génère des variables protégées par un mécanisme garantissant leur unicité, afin d’éviter des conflits avec des variables définies par d’autres sources. Une variable symbol let s = Symbol("toto"); peut être utilisée comme clé en la plaçant entre crochets, comme ceci,
let obj = {[s]:valeur, ...}
mais dans ce cas on ne peut pas accéder au champ par la notation ".". On y accède seulement par la notation crochet [], comme ceci : obj[s].
[expression] permet de générer des clés dynamiques utilisables ensuite par obj[..] : Exemple :
let i = 0;
let obj = {
["clé_" + i]: "coucou"
};
Résultat : { "clé_0": "coucou" }.
La syntaxe {cle} est un raccourci pour {cle : cle} où le premier cle est la clé et le deuxième est la valeur. Bien évidemment cette syntaxe n'est valide que si la valeur cle existe déjà dans le contexte.
Si une fonction toto() renvoie un objet comme par exemple return {cle1: x, cle2:y} où x, et y sont définis dans le corps de toto, on peut appeler cette fonction comme ceci :
let {cle1:a, cle2} = toto(); ce qui définira les 2 variables a qui vaudra x et cle2 qui vaudra y.
On obtient le tableau des clés par la fonction suivante :
let tabcles = Object.keys(individu);
et on obtient le tableau des valeurs par la fonction :
let tabvaleurs = Object.values(individu)
La déstructuration est surtout utilisée en réception du retour d'une fonction qui renvoie un objet Json qui sont très utilisés pour retourner plusieurs éléments divers.
Supposons la fonction suivante :
function bidon(..) {
..
return {a : 1, b : "bonjour", c : [3,4]};
}
Le plus simple est de récupérer l'objet entier puis de l'exploiter :
let r = bidon();
et on aura r.a = 1, r.b = "bonjour" et r["c"]= [3,4]
Mais on peut également récupérer que les valeurs des champs :
let {a, b, c} = bidon();
et on aura a = 1, b = "bonjour" et c= [3,4]
ou encore en attribuant la valeur à une variable d'un nom différent :
let {a:x, b:y, c} = bidon();
et on aura x = 1, y = "bonjour" et c= [3,4]
L'ordre n'importe pas et on peut omettre des éléments :
let {c, b:y} = bidon();
et on aura y = "bonjour" et c= [3,4]
On ne peut accéder à une valeur qu'avec par le nom de sa clé.
Il permet de déstructurer l'objet, ce qui permet ensuite d'en construire de nouveaux :
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
// Assemblage des 2 objets après déstructuration
const mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj); // Output: { a: 1, b: 3, c: 4 }
// Clone d'un objet
const clonedObj = { ...obj1 };
console.log(clonedObj); // Output: { a: 1, b: 2 }
Un objet est élément qui possède zéro, un ou plusieurs membres qui sont des valeurs, des fonctions ou d'autres objtes, appelés membre attribut, membre propriété, membre méthode. Il est généralement représenté par un nom unique qui représente sa définition (le nom de sa classe) et ses réprésentants ont nom quelconque choisi par l'utilisateur, appelée l'instance qui permet de le manipuler et qui réprésente son instance en mémoire.
En javascript, la distinction entre membre propriété et membre méthode n'est pas évidente (existe-t-elle?). Tout est objet, aussi bien les propriétés que les méthodes, car les fonctions sont aussi des objets en javascript. Ainsi étant donné un membre de type méthode ou fonction, si on l'invoque avec ses parenthèses et arguments, on active son exécution, et si on l'invoque sans parenthèse ni argument, on récupère le membre propriété, qui est l'équivalent de son adresse, que l'on peut transmettre ailleurs. Il semble qu'on puisse utiliser le mot d'attribut pour couvrir cette dualité. Ma connaissance du javascript étant assez superficielle, je suis plutôt dans le flou sur ce sujet.
En javascript, tout est objet, même les résultats partiels d’un calcul, à l’exception éventuelle des nombres entiers littéraux (1, 25,...).Tous les objets héritent d’une classe d’objet père supérieur qui lègue à ses enfants un certain nombre de propriétés comme valueOf() et toString().
Un objet est une instance de la classe, c’est-à-dire un représentant de cette classe qui est en mémoire. Il est créé de manière immédiate à partir de la classe à l’aide du mot clé new, comme ceci : var petitPrince = new Livre ; // déclaration de l’objet petitPrince de la classe Livre
On l’appelle et on le manipule à l’aide du nom de la variable qui l’encapsule : console.log(petitPrince.auteur); // devrait donner "Antoine de Saint-Exupéry".
Au sein d'une méthode de l'objet le mot clé this fait référence à l'instance courante de l'objet. Il permet d'accéder aux propriétés ou méthodes de l'objet sous une des 2 formes suivantes :
this.nomProp ou this["nomProp"].
C’est la classe mère de toutes les classes qui héritent de ses propriétés. On en a déjà vu 2 : les méthodes valueOf() et toString(). Ainsi on peut construire un Objet sans autre propriété particulière comme ceci : var obj = new Object();
globalThis : renvoie l'objet global de plus haut niveau
Dans le code html, this pointe l'élément dans lequel la fonction a été activée. Par exemple, dans la ligne suivante :
<img src='image.jpg' onclick='nom_fonction(this,parametre2)' />
le paramètre this est pointe sur l'élément émetteur (img), ce qui permet de ne pas fournir d'id à cet élément, id qui est normalement nécessaire pour accéder à son émetteur :
<img src='image.jpg' id="idElem" onclick='nom_fonction(this,parametre2)' />
au moyen de document.getElementById('idElem').
En javascript, on peut utiliser la déclaration d'une fonction pour déclarer une classe. Pour cela , il suffit d'utiliser le mot clé this pour désigner les propriétés et méthodes de la classe. Exemple :
function Personne(name, age, genre)
{
this.nom = name ;
this.age = age ;
this.sexe = genre ;
this.salutation = function()
{ alert("Bonjour ! Je m’appelle " + this.nom + ‘.’); };
};
On construit un objet comme ceci :
var smf = new Personne(’Smith’, 18, ‘feminin’);
Quand le mot clé this est utilisé dans une fonction cette fonction sert à définir une classe. Dans le champ de cette classe le mot clé this sert à désigner l’instance de l’objet actuellement actif au moment de l'exécution du code.
On peut à tout moment ajouter de nouveaux membres à un objet, sans que ce soit fait au niveau d'une classe. Exemple :
personne['yeux'] = 'noisette'
personne.auRevoir = function() { alert("Bye bye tout le monde !"); }
et même un nouvel attribut dont la valeur est a priori inconnue, contenue dans une variable dont la valeur sera par exemple fournie par l'usager après lecture dans un input. Exemple :
personne[newchamp] = newValeur;
Avec par ex newChamp = 'hauteur' et newValeur = 1.75 on a le nouveau champ instancié personne.hauteur = 1,75. Remarquons qu'on n'aurait pas pu écrire personne.newchamp = newValeur; car aurait crée le champ newChamp et non pas le champ hauteur.
Autre exemple :
let t = new Array(4,2,1);
t.lentgth ; // ==> 3
t.truc = "bidule" ;
t.length ; // ==> 3 car la propriété truc n’est pas intégrée à la partie tableau t.truc ; // ==> "bidule" car la proriété truc est bien instanciée
Tous les objets possèdent la propriété prototype qui permet d’ajouter une propriété à une classe ou à un objet.
Ainsi l’instruction : Personne.prototype.prenom ="" ;
ajoute la propriété prenom à la classe Personne mais sconstructeur Personne(name, age, genre) qui n'est pas modifié, ne permettra pas de l’initialiser ce qui diminue son intérêt car on peut à tout moment ajouter smf.prenom = ‘Jane’ ; à un objet de la classe Personne.
Par contre cela peut être intéressant pour une valeur commune à tous les objets comme :
Personne.prototype.race="humaine" ;
On aura alors smf.race => ‘humaine’, même lorsque le prototype est ajouté après la création de l’objet.
Ces instructions prototype peuvent être utilisés entre les {} dans le corps de la définition de la fonction, où n’importe où après sa déclaration. Leur effet sera rétro-actif, comme si elles étaient dans le champ de définition de la fonction.
Le mécanisme prototype est intéressant pour l’ajout d’une fonction. Ainsi :
Personne.prototype.bio = function() {console.log(this.nom + ‘ est de race ‘ + this.race)};
permettra d’obtenir Smith est de race humaine, même si le prototype est ajouté après la création de l’objet.
La modification d’une propriété (native ou prototype) au niveau d’un objet est possible. Cette modification est sans effet au niveau de la classe et au niveau des autres objets la classe.
Les propriétés natives (nom, age, sexe, salutation) sont recopiés dans tous les objets crées à partir de la classe Personne, mais les propriétés prototype prenom et bio, qui sont les mêmes pour tous les objets de la classe Personne n’y sont pas recopiés . On fait une économie de mémoire qui peut être intéressante sur de gros projets.
Les propriétés créées avec prototype sont des propriétés de la classe qui s’apparentent un peu aux membres statiques du C++ : Ainsi, si aucune propriété dynamique n'est accédée via un this, on peut créer via l'attribut prototype des membres statiques :
Personne.prototype.salutation = "bonjour";
Cette proprité peut être utilisée directement sur la classe sans définir dobjet instance :
Document.write(Personne.prototype.salutation);
On peut définir une nouvelle classe qui incorpore les propriétés d’une classe préexistante, comme par exemple la classe Professeur qui incorpore les propriétés de la classe Personne, puisqu’ils en sont.
Quand on définit la classe Professeur, on incorpore les propriétés de la classe Personne en appellant la méthode Personne.call(this, args de Personne). Exemple :
function Professeur(nom, age, genre, matiere) {
Personne.call(this, nom, age, genre);
this.matiere = matiere ;
}
var mi = new Professeur(’Pierre’,30,’homme’,’math’);
Dans l’exemple précédent Professeur hérite de toutes les propriétés de Personne qui sont définies à l’intérieur de la fonction, (entre les {} à cause de l’appel de cette fonction par le call), mais pas les prototypes ajoutés à l’extérieur de cette fonction, par exemple la fonction bio(). si elle a été ajouté à l’extérieur.
Pour qu’il hérite de cette fonction, il faut lui attribuer le prototype de sa classe mère avec la méthode create comme ceci :
Professeur.prototype = Object.create(Personne.prototype); Professeur.prototype.constructor = Professeur ; La première ligne remplace le constructeur de Professeur par celui de son parent Personne, ce qui permmettra d’accéder aux prototypes rajoutés à l’extérieur de la fonction Personne et la 2ème ligne permet de rendre à Professeur son propre constructeur.
Dans une classe fille on peut modifier une propriété héritée des parents, il suffit de récrire la valeur de la propriété fille.prototype.proprieteamodifier. Elle sera utilisée pour les objets de la classe fille.
On peut appeler la fonction définie au niveau du parent par le biais de la méthode super. Exemple : une classe Professeur qui hérite de la classe Personne :
function Professeur(prenom, nom, age, genre, interets, matiere) {
Personne.call(this, prenom, nom, age, genre, interets);
this.matiere = matiere;
}
Exemple de surcharge de la méthode salutation :
Professeur.prototype.saluer = function() {
var prefix;
if(this.genre==='mâle'||this.genre==='Mâle'
||this.genre==='m'||this.genre==='M') { prefix = 'M.';}
else if(this.genre==='femelle'||this.genre==='Femelle'
||this.genre==='f'||this.genre ==='F') { prefix = 'Mme';}
else { prefix = '';}
alert("Bonjour. Mon nom est " + prefix + " " + this.nom_complet.nom +
", et j'enseigne " + this.matiere + ".");
};
On peut utiliser la classe générique Object pour créer une instance d'une classe de typePersonne :
Approche 1 :
var personne2 = new Object();
personne1.nom = 'Chris';
personne1['age'] = 38;
personne1.salutation = function()
{
alert('Bonjour ! Je m\'appelle ' + this.nom + '.');
};
Approche 2 :
var personne3 = new Object({
nom: 'Chris',
age: 38,
salutation: function() {
alert('Bonjour ! Je m\'appelle ' + this.nom + '.');
}
});
Si on a créé un objet unique, via une méthode explicite par exemple, sans créer de constructeur de cette classe, on peut créer un objet du même type avec la méthode create de la classe Object
Exemple :
var personne4 = Object.create(personne3);
Nouveau depuis ES6.
Soit un tableau :
const fruits = ['pomme', 'banane', 'cerise'];
on peut affecter chacun de ses éléments à une variable comme ceci :
const [v1, v2, v3] = fruits;
ce qui donnera v1 = 'pomme', v2 = 'banane', v3 = 'cerise';
La syntaxe "..." est utilisée pour préfixer des tableaux à concaténer :
const fruits = ['pomme', 'banane'];
const légumes = ['carotte', 'brocoli'];
// Fusionner les tableaux en un seul
const nourriture = [...fruits, ...légumes];
Soit un objet :
const personne = { nom: 'Alice', age: 25, ville: 'Paris' };
on peut affecter chacun de ses éléments à une variable comme ceci :
const { x, y, z } = personne;
ce qui donnera x = 'Alice', y = 25, z = 'Paris';
Comme pour les tableaux, a syntaxe "..." est utilisée pour préfixer des objets à concaténer :
const ident = { nom: 'Alice', age: 25 };
const adr = { ville: 'Paris', pays: 'France' };
// Fusionner les objets
const personne = { ...ident, ...adr };
En 2015 une nouvelle refonte de javascript est intervenue reposant sur un standard dit ECMA (European Computer Manufacturers Association) pour les langages de script dite ECMAScript 2015 ou ES6 (EcmaScript n°6). Cette refonte introduit le mot clé class pour nommer les classes, le mot clé constructor pour les initialiser et le mot clé extends pour les étendre.
class A {
constructor(argx) {this.x = argx ;}
methode(argy) {console.log(this.x, argy);}
}
Dans les méthodes on ne peut accéder aux propriétés de l'instance que par le biais du mot-clé this.
Création d'une instance et appel des méthodes de classe :
let a = new A(1);
a.methode(2); ==> 1, 2
Les propriétés et méthodes d'une classe sont d'un des 3 types suivants :
•static x = 2; déclarée en dehors de la méthode constructor. C'est une propriété de classe globale qui peut être accédée à l'intérieur et extérieur de la classe uniquement via le nom de la classe (sans utiliser de nom d'instance).
•#y = 5; déclaré en dehors de la méthode constructor. C'est une propriété de classe privée qui ne peut être accédée qu'à l'intérieur de la classe via this.#y.
•this.z = arg; déclaré dans la méthode constructor. C'est une propriété de classe ordinaire qui peut être accédée à l'intérieur et extérieur de la classe via le nom dune instance de la classe crée par un new.
Une méthode précédée du mot clé static dans sa déclaration ne peut pas accéder à des propriétés d'instances via le mot clé this. Pour accéder aux propriétés de l'instance celle-ci doit lui être passé en argument.
class A{
static x = 2;
#y = 5;
constructor(arg){
this.z = arg;
console.log("x, y, z = ",A.x, this.#y, this.z);
}
vasy(){console.log("x, y, z = ",A.x, this.#y, this.z);}
static vaya() {console.log("x, z = ",A.x, this.z);} // #y inaccessible ici
}
let a = new A(7); // imprime 2 5 7
A.x = 9;
a.vasy(); // appel fonction d'instance : imprime 9 5 7
A.vaya(); // appel fonction de classe : imprime 9 undéfined
Remarque : Une méthode peut être crée dans le constructor en l'affectant à une variable d'instance, comme ceci :
class A {
constructor(argc) {
this.argc = argc;
this.methode1 = function(arg1) {
console.log(this.argc, arg1);
};
}
au lieu de la déclarer à la suite du constructeur, comme ceci :
methode2(arg1) {
console.log(this.argc, arg1); }
Il n'y a pas de différence de comportement, mais une methode1 est créée en mémoire pour chaque instance alors qu'une seule methode2 est créée pour la classe. De plus le prototype de la classe ne mentionne pas methode1 qui ne sera donc pas héritée.
On peut créer une classe purement statique, avec toutes ses propriétés et méthodes déclarées statiques. Exemple :
class Mystat {
static E = 2.71828;
static addition(a, b) {return a + b;}
static multiplication(a, b) {return a * b;}
}
Si on veut s'assurer qu'elle ne sera pas instancier on peut ajouter :
class Mystat {
constructor() {throw new Error("Cette classe ne peut pas être instanciée"); }
static E = 2.71828;
static addition(a, b) {return a + b;}
static multiplication(a, b) {return a * b;}
}
Remarque : Dans le corps des méthodes les autres propriétés ou méthodes sont accédés via Mystat. ou this..
Alternative : Un objet "namespace :
Mystat = {
E : 2.71828,
increm() {return Mystat.E + 1;},
addition(a, b) {return a + b;},
multiplication(a, b) {return a * b;}
}
La définition de la classe A suivante :
function A(argx) {
this.x = argx;
this.methode = function(argy){console.log(this.x, argy);};
}
est équivalente à la nouvelle approche suivante :
class A {
constructor(argx) {
this.x = argx;
this.methode = function(argy){console.log(this.x, argy);};
}
}
qui crée la méthode dans le constructeur ce qui a les inconvénient précédemment mentionnées.
Avec l'ancienne méthode ces inconvénients étaient levés en ne définissant pas la méthode dans le prototype de la classe :
function A(argx) { this.x = argx;}
mais en la rajoutant au prototype ensuite :
A.prototype.methode = function(argy) {console.log(this.x, argy);};
Dans ca cas, c'est comme si on avait défini la classe comme ceci :
class A {
constructor(argx) {this.x = argx;}
methode(argy){console.log(this.x, argy);}
}
class Lapin extends Animal {
secacher() {
alert(`${this.nom} se cache !`);
}
}
let rabbit = new Lapin("Bunny");
rabbit.courir(50);
rabbit.arreter();
rabbit.secacher();
L'attibut constructor est également une propriété de toutes les instances de classe. On peut s’en servir pour créer des objets d’une classe à partir d’un de ses objets, quand on ne connait pas le nom de la classe.
Exemple : On connaît l’objet rabbit mais on ne connait pas le nom de sa classe (Lapin).
Sans connaître le nom de la classe on peut créer une autre instane dee Lapin comme suit :
var lapino = new rabbit.constructor("Petit Bunny");
ou en deux temps comme ceci :
var createurRabbit = rabbit.constructor;
var lapino = new createurRabbit("Petit Bunny");
Le mode strict est une variante de javascript mise en service par l'instruction "use strict"; placée avant toutes les autres, mais ce mode est automatiquement mis en oeuvre dans le corps des classes et dans les script de type module : <script type="module">.
Dans un script ordinaire (non strict), ce mode être mis en action à l'intérieur d'une fonction en plaçant l'instruction"use strict"; en premier dans le corps de la fonction.
Le mode strict impose :
•déclarer toutes les variables avec let, var ou const.
Le mode strict interdit :
•nommer une variable avec un mot-clé réservé.
•l'usage du mot-clé with(obj)
•de réassigner une variable déclarée const.
•de modifier une propriété non writable (voir exemple ci-après)
'use strict';
const obj = {};
Object.defineProperty(obj, 'a', {
value: 10,
writable: false
});
obj.a = 20; // erreur en mode strict
Autre changement très génant :
Les fonctions ne sont pas visibles dans l’espace global (window), donc on ne peut plus les appeler directement via onclick="..." dans le HTML. Cela est dû au fait que this implicitement attribué à window dans la portée globale en standard (non strict) devient indéfini en strict.
La ligne <button onclick="toto()">, va généréer l'erreur "Uncaught ReferenceError: getDistance is not defined" que l'on contournera en faisant window.toto = toto dans javascript (on l'affecte à l'objet window), ce qui n'est pas une pratique conseillée. Si onclick devait utiliser une fonction définie dans le javascript, il est conseillé de ne pas utiliser onclick et d'utiliser un addEventListener à la place.
En mode satndard (non strict) une variable n’est pas de type figée et peut être redéfinie avec un autre type. Ce sont des variants qui peuvent encapsuler un entier, un booléen, un réel, un string ou un objet.
En mode non strict on peut déclarer une variable simplement comme ceci : x = 1; Mais il est conseillé, comme en mode strict d'utiliser les spécificateurs de portée let, const et éventuellemnt var.
Les variables définies par let et const ont une portée limitée à l'intérieur du bloc dans lequel elles sont définies (bloc fonction, bloc classe, bloc if, bloc while, bloc anonyme...). Elles ne peuvent être utilisées qu'après leur déclaration. Si elles sont définies dans la portée globale, elle seront accessible partout (y compris dans les définitions de fonction et classes internes à ce module), après la ligne de leur déclaration.
Exemples :
for (let i = 0 ; i < 2 ; i++) { .. }
La variable i n’existe pas à l’extérieur du bloc for, ou si elle existait déjà, elle n’y sera pas modifiée.
A chaque itération une nouvelle variable i est créée.
En mode strict, une variable const ne peut être réassignée à une nouvelle valeur.
let x = 10;
x = 20; // Autorisé
const y = 30;
y = 40; // INTERDIT
const z = [50, 640];
z[1] = 70; // Autorisé
•tout le fichier si elle est définie en dehors de toute fonction ou classe,
•tout l’intérieur de la fonction à l’intérieur de laquelle elle est définie (et pas à l’extérieur).
Une variable définie avec un let dans un bloc if n'existe plus à l'extérieur, alors qu'une variable définie par un var existera à l'extérieur du bloc. Les blocs ne constituent pas une frontière pour la variable déclarée avec var à l'exception de ceux des fonctions et des classes.
Une variable var définie dans une boucle est partagée entre toutes les itérations :
for (var j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100); // Affiche 3, 3, 3
}
En effet, le setTimeout diffère l'exécution de console.log(j) de 100 ms, mais ne bloque pas le thread qui continue, incrémente j et lance les 2 autres avant que le premier ne soit exécuté. Quand ils sont exécutés la variable j qui n'existe qu'en un seul exemplaire vaut 3 d'où le résultat !
Avec let, on aurait 0, 1, 2 car chaque console.log(j) accèdera à un j différent généré à chaque itération.
La déclaration var x ; peuvent être faite n’importe où dans un contexte donné, même après l’affectation de la variable , comme ci-après :
x = 1 ;
...
var x ;
Remarque : En mode strict la première assignation x = 1 ; sans déclaration est acceptée car sa déclaration est trouvée par la suite.
Une variable x déclaré var à l'extérieur de toute fonction et classe est ajoutée au contexte et y sera accessible par x et window.x et par this.x en dehors de toute fonction et classe, ou this est relatif à l'instance en cours.
L'hoisting (élévation) est un comportement du code qui consiste à faire comme si certaines déclarations étaient faites au tout début du code avant toute exécution, ce qui permet par exemple de définir des fonctions en fin de code et de les utiliser partout.
Les variables déclarées par let, const et var voient aussi leur existence élevée au tout début du code, précédent l'exécution ce qui permet, dans le cas de var d'assigner la variable x = 1 alors que sa déclaration figure plus loin dans le code. On dit dans ce cas qu'il n'y a pas de TDZ (temporal dead zone = zone morte temporelle), la variable est immédiatement disponible.
Par contre dans le cas de let et const, une TDZ (un délai) est appliquée qui empêche l'assignation des variables avant la ligne ou intervient leur déclaration.
X=1 ; initialise une variable. Si elle est non déclarée par var ou par let ou const, ce qui est une erreur en mode strict, elle sera globale, même si elle est initialisée à l’intérieur d’une fonction. Il faut donc l’éviter, sauf si c’est ce que l’on cherche à obtenir.
Un fichier script "bidon.js" contenant des variales, fonctions, classes, comme celui-ci :
Variante 1 :
const uneVariable = 3.14
function uneFonction() {console.log("unefonc appelée !");}
class UneClasse {
constructor() {console.log("Instance de uneClasse créée !");}
}
ou bien comme ceci (variante pour la définition des fonctions et classes :
Variante 2 :
const uneVariable = 3.14;
const uneFonction = function(){console.log("unefonc appelée !");};
const UneClasse = class {
constructor() {console.log("Instance de UneClasse créée !");}
};
peut être importé dans un fichier html avant un autre script comme suit :
<script src="./bidon.js"></script>
<script>
console.log(uneVariable);
const a = new UneClasse();
uneFonction();
</script>
On regrouper les éléments de la Variante 2 , mais pas de la (Variante 1) dans un objet comme ceci :
const Bidon = {
uneVariable : 3.14,
uneFonction : function(){console.log("unefonc appelée !");},
UneClasse : class {
constructor() {console.log("Instance de UneClasse créée !");}
}
}
et l'utiliser comme ceci :
<script src = "./bidon.js"></script>
<script>
console.log(Bidon.uneVariable);
const a = new Bidon.UneClasse();
Bidon.uneFonction();
</script>
ou lui donner un alias b pour raccourcir son nom Bidon :
<script>
let b = Bidon;
console.log(b.uneVariable);
const a = new b.UneClasse();
b.uneFonction();
</script>
On peut préférer regrouper les éléments de la Variante 2 dans une classe pour éviter d'avoir à modifier la syntaxe du fichier initial en remplaçant les "=" par des ":", et les ";" par des ",".
const Bidon = class{
uneVariable = 3.14;
uneFonction = function(){console.log("unefonc appelée !");};
UneClasse = class {
constructor() {console.log("Instance de UneClasse créée !");}
};
}
et dans ce cas on les utilisera comme suit :
<script src = "./bidon.js"></script>
<script>
let b = new Bidon;
console.log(b.uneVariable);
const a = new b.UneClasse();
b.uneFonction();
</script>
VOIR : https://fr.javascript.info/modules-intro
Si on ne désire importer que certaines fonctions d'une bibliothèque il existe une méthode qui passe par l'utilisation des mots clés export et import et qui nécessite que le script importeur soit déclaré avec l'attribut type="module" comme ceci :
<script type="module">
sinon on aura une erreur du style : Uncaught SyntaxError: Cannot use import statement outside a module.
REMARQUES :
•Les script de type "module" ne fonctionnement qu'en mode javascript strict.
•CES IMPORT ET EXPORT NE FONCTIONNENT QUE VIA UN SERVEUR, sinon en local on aura un message d'erreur : Blocage d’une requête multiorigine (Cross-Origin Request) : la requête CORS n’utilise pas HTTP.
•L'adresse du fichier JS contenant les objets à importer doit être - soit une URL externe - soit une adresse relative locale. Attention les url relatives dans le même répertoire doivent commencer par "./"
•let str = import.meta.url; fournit l''url de la page actuelle.
Tous les objets à exporter sont déclarés export dans le fichier1.js :
export const uneVariable = 3.141;
export function fonction(){console.log("fonction appelée !");};
export const uneFonction = function toto(){console.log("toto appelé !");};
export const autreFonction = function(){console.log("anonyme appelée !");};
export class Truc{
constructor() {console.log("Instance de Truc créée !");}};
export const uneTruc = class Classe{
constructor() {console.log("Instance de Classe créée !");}};
export const autreTruc = class{
constructor() {console.log("Instance de anonyme créée !");}};
ou bien on liste ceux que l'on veut exporter dans une instruction : export {..}
const uneVariable = 3.141;
function fonction(){console.log("fonction appelée !");};
const uneFonction = function toto(){console.log("toto appelé !");};
const autreFonction = function(){console.log("anonyme appelée !");};
class Truc{
constructor() {console.log("Instance de Truc créée !");}};
const uneTruc = class Classe{
constructor() {console.log("Instance de Classe créée !");}};
const autreTruc = class{
constructor() {console.log("Instance de anonyme créée !");}};
export {uneVariable, fonction, uneFonction, autreFonction, Truc, uneTruc, autreTruc};
Remarque : Comme dans le cas de le cas de l'ancienne méthode, on peut regrouper des éléments dans un objet ou dans une classe, ce qui permer d'accéder via l'alias qui sera donné aux éléments qu'elle contient.
Ensuite on peut les importer dans un autre script d'un autre fichier comme ceci :
import {uneVariable, fonction, uneFonction, autreFonction, Truc, uneTruc, autreTruc} from "./fichier1.js";
ce qui permet de les utiliser comme s'ils avaient été déclarés dans ce script.
Inconvénient, il faut nommer tous les éléments importés.
On importe tous les exports du fichier1.js sous un nom unique qui servira d'objet conteneur :
<script type="module">
import * as U from "./bidon.js"
console.log("U.uneVariable = "+U.uneVariable);
U.fonction();
U.uneFonction();
U.autreFonction();
new U.Truc();
new U.uneTruc();
new U.autreTruc();
</script>
En nommant default la liste d'exportation, comme suit :
export default {uneVariable, fonction,uneFonction, autreFonction, Truc, uneTruc, autreTruc} ;
On lui attribue un nom à l'importation qui permet d'accéder aux éléments via ce nom, proche du cas de l'importation de la liste :
<script type="module">
import U from "./bidon.js"
console.log("U.uneVariable = "+U.uneVariable);
U.fonction();
U.uneFonction();
U.autreFonction();
new U.Truc();
new U.uneTruc();
new U.autreTruc();
</script>
Remarquer que dans le cas précédent on avait importé tous les éléments de la liste par :
import * as U from "./bidon.js"
et que ici on mes importe par :
import U from "./bidon.js"
Dans les 2 cas précédents utilisants, import * as U from "./bidon.js" ou import U from "./bidon.js" de l'exportation default, on peut déstructurer le conteneur pour pouvoir utiliser les éléments importés sans avoir à les préfixer par le nom du conteneur (ici U), comme ceci :
const {uneVariable, fonction,uneFonction, autreFonction, Truc, uneTruc, autreTruc} = U;
et ensuite on peut accèder directement aux éléments, sans le préfixe du conteneur :
console.log("uneVariable = "+uneVariable);
fonction();
uneFonction();
autreFonction();
new Truc();
new uneTruc();
new autreTruc();
Remarque : ils sont également accessible via "U.".
Pour importer des fonctionalités d'un script bidon.js, il y a de nombreuses options. Voici deux options simples :
1.<script src="./bidon.js"></script> : On concatène tout le script au fichier en cours. Les éléments du script bidon.js peuvent être séparés en vrac ou regroupés dans un objet Json ou une classe. S'ils sont en vrac, il y a un risque de télescopage avec des noms existants,
2.import * as U from "./bidon.js": Dans un script type="module". Il faut avoir déclaré export toutes les fonctionnalités que l'on veut exporter. Pas de risque de télescopage car on accèdes aux fonctionalités par des U.. La déclaration export peut-être individuelle par fonctionnalité, ou en un ou plusieurs export d'une liste d'éléments à exporter,entre accolades{}.
3.import U from "./bidon.js" : Dans ce cas on a affaire à un export default qui est soit suivi de la liste unique des fonctionalités à exporter {f1, f2}, soit suivi d'un nom d'objet qui liste ces fonctionnalités.
Dans le dernier cas, la liste peut être construite par concaténation.
Voici deux exemples des cas les plus intéressants :
Cas 2 sans liste :
export const uneVariable = 3.141;
export function fonction(){console.log("fonction appelée !");};
export const uneFonction = function toto(){console.log("Toto appelé !");};
export const autreFonction = function(){console.log("anonyme appelée !");};
export class Truc{
constructor() {console.log("Instance de Trac créée !");}};
export const uneTruc = class Classe{
constructor() {console.log("Instance de Classe créée !");}};
export const autreTruc = class{
constructor() {console.log("Instance de anonyme créée !");}};
Cas 2 avec listes :
const uneVariable = 3.141;
function fonction(){console.log("fonction appelée !");};
const uneFonction = function toto(){console.log("Toto appelé !");};
const autreFonction = function(){console.log("anonyme appelée !");};
class Truc{
constructor() {console.log("Instance de Trac créée !");}};
const uneTruc = class Classe{
constructor() {console.log("Instance de Classe créée !");}};
const autreTruc = class{
constructor() {console.log("Instance de anonyme créée !");}};
export {uneVariable, fonction,uneFonction, autreFonction};
export {Truc, uneTruc, autreTruc};
Utilisation Cas 2 :
<script type="module">
import * as U from "./bidon.js"
console.log("uneVariable = "+U.uneVariable);
U.fonction();
U.uneFonction();
U.autreFonction();
new U.Truc();
new U.uneTruc();
new U.autreTruc();
</script>
</body>
Cas 3 defaulf avec liste concaténée.
const uneVariable = 3.141;
function fonction(){console.log("fonction appelée !");};
const uneFonction = function toto(){console.log("Toto appelé !");};
const autreFonction = function(){console.log("anonyme appelée !");};
class Truc{
constructor() {console.log("Instance de Troc créée !");}};
const uneTruc = class Classe{
constructor() {console.log("Instance de Classe créée !");}};
const autreTruc = class{
constructor() {console.log("Instance de anonyme créée !");}};
const utils1 = {uneVariable, fonction,uneFonction, autreFonction}
const utils2 = {Truc, uneTruc, autreTruc};
export default { ...utils1, ...utils2 };
Variante
const utils = { ...utils1, ...utils2 };
export default utils;
Utilisation :
<script type="module">
import U from "./bidon.js"
console.log("uneVariable = "+U.uneVariable);
U.fonction();
U.uneFonction();
U.autreFonction();
new U.Truc();
new U.uneTruc();
new U.autreTruc();
</script>
L’interface de type windows avec l’utilisateur se fait dans des pages dont le contenu est décrit en suivant les modèles HTML et CSS. De quoi s’agit-il. Pour faire simple HTML et CSS définissent des noms tels que body (le corps de la page), main (le container principal de la page, facultatif), section (un paragraphe), div (une division, zone plus ou moins quelconque)... pour les différentes parties d’un affichage et des noms comme color (couleur), width (largeur)... pour leurs caractéristiques. Les modèles HTML et CSS sont imbriqués, mais grossièrement le HTML défini le contenu (le texte qui est écrit, les images qui sont affichées) alors que le CSS défini la manière dont ce contenu est affiché (taille des caractères, couleur du fond de la page...).
HTML signifie HyperText Markup Language que l’on peut traduire par langage de balises pour l’hypertexte. C’est bien compliqué pour dire que la description des pages utilise un système de balises ouvrantes fermantes imbriquées. Exemple, fichier HTML minimal, d’extension .html : <html> <body> Une page web très simple </body> </html>
CSS signifie signifie Cascading Style Sheets que l’on peut traduire par feuille de style en cascade. La aussi c’est bien compliqué pour dire que le CSS va préciser le style de certaines zones.
Exemple de CSS :
body{width : 90 %;}
pour expliquer que l’affichage ne se fera que sur 90 % de la largeur de l’écran.
Les instructions de style (c’est-à-dire le CSS) n’utilisent pas des balises, mais une structuration à base d’accolades ouvrantes { et fermantes }.
Les instructions de style peuvent se situer :
•dans un fichier d’extension .css, .Il sera inclus dans le fichier .html par le biais d’une balise <link ...> qui indique un lien sur une partie à inclure ex : <link rel="stylesheet" type="text/css" href="fichier.css" media="all">
•dans la section <head>...</head> du fichier *.html entre les balises <style>...</style>,
•localement dans une section au moyen de l’attribut style. Ex :
<div style = "font-size :16pt">Gros coucou</div>
HTML et CSS permettent de faire des pages très complexes sans javascript. Mais à tous les éléments définis par ces deux langages correspondent des objets javascripts qui permettent de les utiliser en javascript, sans code HTML ou CSS. Le problème est qu’il n’y a pas de documentations spécifique à javascript pour les API (interfaces de programmation d’application). Il faut aller regarder dans la documentation HTML/CSS pour afficher des boutons, des cases à cocher, des listes déroulantes... et en déduire l’utilisation en javascript. Cela se fait par le biais d’un modèle appelé le DOM (Document Object Model) que l’on peut traduire par modèle objet de document et qui est constitué des objets et fonctions JavaScript qui permettent d’accéder à tous les éléments HTML/CSS, de les créer, de les modifier... et de faire des pages plus de dynamiques et interactives.
L’objet Window est un peu à part du DOM. Il donne accès à certains élément de l’écran. Il est créé lors de l’ouverture du navigateur et contient toutes les propriétés et les méthodes de gestion de la fenêtre. Il permet de récupérer la taille de la fenêtre : window.innerWidth et window.innerHeight, et de procéder à l’adaptation de son contenu en conséquence, par exemple en surchargeant la méthode window.onresize().
Exemple de re-dimension de l’élément myDiv : à la dimension de la fenêtre :
window.onresize = function() { let wW = window.innerWidth ; let hW = window.innerHeight ; myDiv.style.width = wW + ‘px’ ; myDiv.style.height = hW + ‘px’ ; }
On a déjà vu deux méthodes de l’objet window, très utilisées pour afficher des messages d’alerte ou de débogage : window.alert(..) et window.console.log(...)
Remarque : Les propriétés et méthodes de window peuvent être appelées sans avoir à préciser l’objet window. Par exemple alert(...) ou console.log(..).
Accès au nom de la page en cours :
var nom = window.location.pathname; // nom compet
nom = nom.split("/"); // Découpage des répertoires
nom = nom1[nom.length - 1]; // Seulement le dernier
nom = nom.substr(0, nom.lastIndexOf(".")); // Supprime l'extension
nom = nom.replace(new RegExp("(%20|_|-)", "g"), ""); // ????
L'objet navigator : window.navigator.
Cet objet contient les propriétés du navigateur (nom, version, langue, etc.). De moins en moins utilisé, on utilisera plutôt certaines propriétés de l'objet document pour réaliser ces tests.
L’objet Document est l’objet de base qui représente le corps du document. Il permet de manipuler les éléments HTML et CSS qui le composent.
Nous avons vu la méthode write permet d’afficher du texte dans la page web sans aucune mise en forme html : document.write("Bonjour tout le monde !"); C’est une méthode à utiliser uniquement pour du débogage, car elle écrit n’importe où dans le document courant.
Le Document Object Model (DOM) est composé d’éléments qui sont organisés en une arborescence hiérachique de nœuds (node), comme un arbre généalogique.
A part document, les autres éléments/noeuds ont un parent qui est un de ses ancêtres (ancestor). Un élement/noeud peut avoir des enfants (child), des frères et sœurs (sibling). Les éléments sont les zones du DOM comme les zones de métadonnées (<meta>), l’entête (<head>), le corps (body>, les scripts (<script>, les divisions (<div>), les titres (<h1>,<h2>...<h6>), paragraphes (<p>), les canevas (<canvas>)... mais aussi les commentaires, les attributs, les textes…
D’un point de vue informatique le nœud est une interface, c’est-à-dire une liste de méthodes que possèdent tous les objets qui intègrent cette interface. C’est pour cela que lorsqu’on parle des fonctionnalités hiérarchiques des éléments ont parle de nœud, et dès qu’on s’intéresse à leurs aspect dans la page on parle d’élément. Remarque : Il ne faut pas confondre la hiérachie des nœuds où le nœud <body> a pour descendants toutes les zones qui sont à l’intérieur, comme une maison a à l’intérieur des meubles qui ont à l’intérieur des tiroirs... avec la notion d’héritage introduite au niveau des classes (définies par des fonctions exemple la classe Personne) où on définit une classe enfant (exemple la classe Professeur) en ajoutant des propriétés supplémentaires (exemple la matière enseignée) ou en modifiant des propriété du ou des ancêtres.
L’accès à des éléments du document crées en dehors du JavaScript (dans la partie html par exemple) peut se faire de manière assez simple par :
•document.getElementById("myId") si l’élément a été défini avec l’attribut id="myId". Il est a priori unique.
•Document.getElementsByName("myName) retournera un tableau contenant tous les éléments du document ayant l’attribut name="myName".
•Document.getElementsByTagName(’div’) retournera un tableau contenant tous les éléments de la page ayant le type div. On peut l’utiliser pour tous les types existants p, div, a, img, ....) d’un document :
Remarque : getElementsByName est surtout employé pour les contrôles (input, bouton,... qui sont généralement pourvus de l’attribut name.
La méthode suivante permet de localiser l’ensemble des nœuds de type quelconque (p, div, a, img, ....) d’un document :
•document.querySelector(’div’); récupère l’élément qui correspond au premier nœud div du document.
•Document.querySelectorAll(’div’) retourne la NodeList de tous les éléments div du document.
L’argument de querySelector et querySelectorAll est appelé un sélecteur de nœud. On le met entre guillemets. Plusieurs sélecteurs peuvent être séparés par des virgules.
Partie avancée nécessitant la connaissance du CSS Un sélecteur peut être :
•un type simple comme p,
•un id (cad une ancre) comme #debut,
•une classe CSS comme .note,
•une classe dans une zone particulière, comme p.note
•...
Var el = document.querySelector( "#main, #basic, #exclamation, p.note" ); renverra le 1er élément qui correspond. Avec querySelectorAll on aura tous les éléments qui correspondent.
Remarques :
•Ces deux méthodes s’appliquent également aux éléments. Elles renvoient les correspondances dans la descendance de l’élément.
•Le plus simple pour sélectionner un élément précis est de lui attribuer un attribut unique class="xyz" et de le sélectionner grâce à ce nom : document.querySelector(’.xyz’).
La propriété document.links renvoie la collection des nœuds <a> et <area> dans l’ordre où elle apparaît dans le document :
let numb = document.links.length ; // donne le nombre de nœuds trouvés let url = document.links[0].href ; // donne l’url associée au 1er nœud <a> du document. Que l’on peut également accéder par : let url = document.links.item(0).href ; Gestion des éléments/noeuds Les nœuds sont plus que les éléments (les zones containers), ce sont aussi les attributs, textes,...Ils sont repérés par leur type qui correspond à un nombre entier : 1 = ELEMENT_NODE : p, div,…
2 = ATTRIBUTE_NODE
3 = TEXT_NODE
4 = CDATA_SECTION_NODE
8 = COMMENT_NODE
9 = DOCUMENT_NODE
10 = DOCUMENT_TYPE_NODE genre <!DOCTYPE html> 11 : DOCUMENT_FRAGMENT_NODE
On crée un nouveau nœud (p, div, a, img, texte...) comme ceci :
var newPar = document.createElement(’p’);
Mais pour qu’il apparaisse, il faut l’accrocher à un élément existant, en tant qu’enfant de ce nœud, comme ceci :
myDiv.appendChild(newPar); // ajoute un nœud rien qu'un noeud ou
myDiv.append(newPar); // peut prendre plusieurs arguments (noeuds et/ou texte)
ou pour ajouter directement sous le body :
document.body.append(newPar); ou document.body.appendChild(newPar);
Un nœud ne peut être qu’à un seul endroit dans un document. Si on met le nœud newPar ailleurs, comme ceci : myDiv2.appendChild(newPar); il est déplacé et ne figure plus dans myDiv.
Pour l'ajouter ailleurs sans le déplacer, on ajoute un clone :
myDiv2.appendChild(newPar.cloneNode(true));
On duplique un nœud comme ceci :
var div2 = myDiv.cloneNode();
On supprime un nœud comme ceci :
myDiv.remove();
On décroche un nœud comme ceci :
myDiv.removeChild(newPar);
On accède au nœud parent par :
parent = elem.parentNode ;
qui renvoie le parent quand il existe ou null s’il vient d’être crée et n’est pas encore attaché. Le Document (page standard) et les DocumentFragment (pages simpliées) n’ont pas de parent.
Autres fonctions de gestion cloneNode(), contains(), hasChildNodes, insertBefore()…
Tous les éléments HTML elem peuvent être crées en javascript par document.createElement(’elem’), en dehors de quelques éléents particuliers comme une portion ordinaire de texte qui est créée par etxt = document.createTextNode(’Bla bla bla’) et ajouté n’importe où par ici.appendChild(etxt).
Les éléments ‘elem’ que l’on crée le plus souvent en javascript avec createElement(’elem’) sont :
•p : paragraphe
•br : saut de ligne
•div : division
•button : bouton
•input : permet d’insérer les élements suivants définis par elem.type = ‘typeInput’ où typeInput peut valoir :
◦number : champ de saisie d'un nombre
◦range : champ de saisie de 2 nombres
◦texte : : champ de saisie d'un texte
◦password : champ de saisie d'un mot de passe
◦time : champ de saisie sexagésimal
NodeName : renvoie le nom du nœud
nodeType : nb entier spécifiant le type :
nodeValue : renvoie ou permet de fixer la valeur du nœud (quand cela a un sens) chilNodes : tableau des enfant du nœud
firstChild : 1er enfant du nœud ou null
lastChild : dernier enfant du nœud ou null
nexSibling : enfant suivant du parent ou null
previousSibling : enfant précédent du parent ou null isConnected : true si connecté à un parent
parentNode : nœud parent mais se renvoie lui-même dans le cas du Document parentElement : nœud parent, mais renvoie null dans le cas du Document.
TextContent : Le texte du nœud et de ses descendants.
Ainsi document.documentElement.textContent représente tout le texte d’un document. ATTENTION : Assigner le textContent d’un nœud supprime tous les enfants du nœud et les remplace par un seul nœud de texte avec la valeur de chaîne donnée.
Les attributs des éléments du DOM leurs sont, en programmation classique HTML, assignés dans les parties de code en pur HTML ou en CSS. Exemple : Dans la partie pur HTML l’instruction <iframe id="box1" height="50" style="background-color :greenyellow"></iframe> crée un cadre à tout faire iframe (pour afficher du pdf par exemple) de hauteur 50 pixels et de couleur de fond vert -jaune.
Examinons comment on gère en javascript les attributs d'un élément dont on connait l'identifiant donné par exemple par :
b1=document.getElementById("box1");
Certains rares attributs peuvent être directement modifiés dans le code javascript comme suit :
b1.height= 100 ; // ou = "100" ; ou ="100px" conviennent aussi
mais ma plupart le sont via la propriété style :
b1.style.backgroundColor="pink" ;
L’accès direct aux propriétés des éléments du DOM est généralement possible en javascript en lecture et écriture par les syntaxes :
par = elem.nompropriete.nomparametre ;
elem.nompropriete.nomparametre = newpar ;
Les noms des propriétés et paramètres sont les mêmes en javascript et en HTML/CSS à quelques exceptions près : Attention : Les noms composés sont en CSS écrits en kebab case (mots minuscules séparés par des tirets) comme background-color. Ils sont en HTML/javascript écrits en lower camel case (première lettre minuscule, pas de tirets, majuscules devant les mots suivants) comme backgroundColor.
MyDiv.style.bacgroundColor = ‘black" ; <==> background-color : black ; myDiv.style.textAlign = ‘center’ ; <==> text-align : center ;
Il y a très peu d’attributs d’aspect qui sont accessibles en dehors de la propriété style, comme par exemple height dans le cas de <iframe id="box1" height="50"> vu ci-dessus. Pratiquement tous sont accessibles via une propriété de style et c’est la méthode recommandée pour accéder à ces attributs. Par exemple on modifiera les propriétés de l’iframe id="box1" comme suit :
b1=document.getElementById("box1");
b1.style.backgroundColor="pink" ;
b1.style.height= "100px" ; // ou = 100 ; ou ="100" ne marche pas !!!! Remarquer que le paramètre affecté à style.height est systématiquement un string et que dans le cas de grandeurs géométriques (longueurs, angles) il faut préciser l’unité.
les affectations individuelles suivantes
elem.style.width = '100px';
elem.style.height = '100px';
elem.style.border = '1px solid black';
peuvent être regroupées en une seule asignation par le biais d'Object.assign() :
Object.assign(elem.style, {
width: '100px',
height: '100px',
border: '1px solid black' // Assurez-vous que la valeur du border est correcte
});
Les versions récentes de jvascript acceptent l'affectation multiple suivante :
elem.style = "width:100px; height:100px; border:1px solid black";
En plus de ces accès, un attribut de style peut être accédée par getProperty et modifiée par la méthode setProperty :
myDiv.style.setProperty(bacgroundColor, "black"); myDiv.style.setProperty(textAlign, ‘center’); Accès par getAttribute, setAttribute (intérêt limité à l’ajout) Il existe des fonctions spécialisées qui ne sont intéressantes que dans de très rares cas, par exemple pour affecter une nouvel attribut à un objet.
La méthode hasAttribute(name) appliquée à un nœud du DOM permet de savoir si l’attribut name existe. La méthode setAttribute(name, value) permet de modifier n’importe quel attribut existant et en particulier les attributs de style, et même d’ajouter un nouvel attribut quand il n’existait pas. Les méthodes getAttribute et removeAttribute permettent d’obtenir sa valeur ou de le supprimer.
Exemples :
const img = document.querySelector(’img’);//Sélectionne la 1er img console.log(img.hasAttribute(’src’));
console.log(img.getAttribute(’src’));
img.removeAttribute(’src’); // La supprime pour la remplacer img.setAttribute(’src’, ‘https://llibre.fr/thomas/arcenciel.jpg’);
const elem = document.querySelector("p"); // Sélectionne le 1er paragraphe elem.setAttribute("type", "button"); // transforme le paragraphe en bouton elem.setAttribute("name", "bouton");
elem.setAttribute("disabled", ""); // L’attribut disabled ne prend pas de valeur
const myAnchor = document.querySelector("a"); // Sélectionne le 1er <a> myAnchor.setAttribute("href", "https://llibre.fr"); // modifie le lien
Utiliser cette méthode pour modifier un attribut de style est déconseillé car : myDiv.setAttribute("style", "background-color :black ;"); va effacer toutes les autres propriétés, et entrainer celles par défaut comme text-align : left ;
La plupart des éléments du DOM peuvent contenir du texte que l'on peut modifier à l'aide des 3 propriétés suivantes : innerHTML, innerText et textContent.
•innerHTML fournit en lecture le texte complet avec les balises HTML et en écriture il interprète les balises et affiche le texte formaté..
•textContent fournit en lecture le texte le texte brut débarrassé de tout style, balises... de même qu'en écriture, mais il conserve les espaces multiples et sauts de lignes.
•innerText fournit en lecture et en écriture le texte le texte brut débarrassé de tout style, balises... Les sauts de lignes et espaces superflus sont également éliminés.
Pour afficher une partie de page web à partir de son code, il faut utiliser innerHTML qui conserve les balises en lecture et les interprête au moment de l'affichage.
Pour récupérer du texte brut en lecture on peut utiliser innerText ou textContent (qui conserve les sauts de lignes).
Pour afficher du texte brut utilisera indifféremment innerText ou textContent.
On modifie le contenu en lui affectant un nouveau string : myDiv.textContent = "Bonjour" mais on peut ajouter du contenu avec += : myDiv.textContent += " et au revoir".
L'attribut innerHTML est souvent utilisé pour fournir le contenu complet d'un élément. Voici à titre d'exemple la création d'une division contenat un textarea et un bouton :
const container = document.createElement("div");
container.innerHTML = `
<textarea id="codeInput" rows="4" cols="50" placeholder="Écrivez ici.">
</textarea> <br><button onclick="executerCode()">Exécuter</button>`;
document.body.appendChild(container);
Si on trace un cadre à la périphérie de l'élément :
•Les margins sont des marges externes autour du cadre.
•Les paddings (haut, bas, droite et gauche) sont des marges intérieures laissées vierge tout autour de la zone dite cliente, à l’intérieur de celle-ci.
Les scrollbars horizontal et/ou vertical occupent une certaine hauteur et/ou largeur généralement en bas et/ou droite. Ils ne font pas partie de la zone cliente Les borders constituent un cadre tracé autour de la zone cliente et des scrollbars
Les attributs suivants sont immédiatement accessibles (mais non modifiables) :
•clientHeight, clientWidth : n’incluent que les paddings
•offsetHeight, offsetWidth : incluent tout : les paddings, borders & scrollbars, mais que de la partie visible à l’écran
•scrollHeigh, scrollWidth : incluent les padding et éventuellement la partie non visible à cause du scroll.
•OffsetTop, offsetLeft : distances top et left au parent.
La méthode getBoundingClientRect() renvoie un objet ayant pour proriétés left, top, right, bottom, x, y, width, et height :
x == left, y == top donnent la position par rapport au coin du document.
Width et height sont des dimensions hors-tout qui incluent les marges (padding) et les bordures.
Pour modifier ces valeurs il faut passer par les propriétés de style.
La propriété style permet d’accéder en lecture et en modification a de très nombreux paramètres et en particulier à ceux de position et dimension.On y accède en lecture et écriture par elem.style.param où param est un des paramètres suivants :
top, right, bottom et left : C'est approximativement les mêmes définition que pour le BoundingRect précédent, mais en fait elles dépendent de la valeur donnée au paramètre position.
La valeur doit être précisée par un string qui se termine par l’unité de longueur : mm : millimètre
•cm : centimètre
•in : inch = 2,54 cm
•pc : pica, 6 pc = 1 in
•pt : point, 12pt = 1pc, 72pt = 1in
•px : pixel, unité utilisée par défaut
•em : 1 em = hauteur de la fonte locale standard.
•% : en pourcentage de la taille en cours ou par défaut de la propriété considérée.
•vw : 1 % de la largeur de la zone d’affichage (viewport)
•vh : 1 % de la hauteur de la zone d’affichage (viewport)
Les paramètres top, right, bottom et left ne sont pas très pratiques en lecture car c’est le string précédemment positionné qui est renvoyé (avec les unités). Si un string n’a pa été positionné ces paramètres sont vides et ne fournissent donc pas les postions et dimensions de l’élément. Par contre c’est (avec jQuery) le seul moyen de déplacer l’élément.
L'attribut position détermine la signification des attributs précédents :
Position : static | absolute | relative | fixed | sticky
•static : (défaut) positionnement à la suite de l’élément précédent. Dans ce cas top et bottom sont sans effet ni signification)
•relative : positionnement à la suite des éléments précédents de l’arborescence du DOM et décalés par rapport à la position de référence par les top, bottom qui valent 0,0 par défaut. Ne doit pas être utilisé dans une table.
•Absolute : positionnement absolu en fonction de top et bottom, par rapport au document ou à son conteneur si celui-ci à la propiété relative.(1)
•fixed : fixe dans la page (n’est pas scrollé) en fonction de top et bottom.
•sticky : positionnement à la suite (comme relative), puis, lors des scrolls se comporte en fixed au-dela d’un certain seuil.
(1) un objet absolu à l’interieur d’un autre objet absolu est positionné par / au document.
Un objet absolu à l’intérieur d’un objet relatif est positionné en absolu mais par rapport à cet objet père.
Exemple : Attacher un objet au pointeur de la souris.
La position du pointeur de la souris est donnée par les paramètres pageX, pageY de l’objet evenement que nous noterons ev. Ces valeurs sont absolues, c’est-à-dire relative au coin left-top du document.
Si pour l’élément on a choisi style.position == "absolute", on peut positionner l’élément à la position du curseur en faisant :
elem.style.left = e.pageX + document.body.scrollLeft + "px" ; elem.style.top = e.pageY + document.body.scrollTop + "px" ; (au final le membre de gauche sera un string). Les valeurs document.body.scrollXY sont généralement nulles sauf pour de grands documents qui débordent de la page.
Pour cetains types d’éléments ce positionnement est précis, mais pour d’autres il y a un petit décalage difficile à expliquer.
Si on a choisi style.position == "relative", on peut positionner l’élément à la position du curseur en faisant :
elem.style.left = e.pageX + document.body.scrollLeft – gbbX + "px" ; elem.style.top = e.pageY + document.body.scrollTop - gbbY + "px" ; où (gbbX, gbbY) est la position initiale (x,y) ou (left,top) fournie par un elem.getBoundingClientRect(); Effectivement, si la souris est à l’emplacement initial de l’élément, le paramètre e.pageX fournit gbbX qu’il faut retrancher pour générer un déplacement nul de l’élément pour son positionnement en relatif. Cette méthode donne des résultats précis pour tous les éléments que j’ai testés. Il semble que ce soit celle qui est utilisé par jQuery.
Il existe une fonction HTML/CSS très puissante nommée transform qui permet de translater, mettre à l’échelle, tourner... des éléments. On y accède en javascript, comme aux autres propriétés :
Pour faire subir une des transformations suivantes à l’objet toto, on appellera par exemple : toto.style.transform = `rotate(${angle}deg)` ; // `` pour actualiser la variable interne pour le faire tourner de l’angle fourni en degrés
Voici quelques unes des différentes transformations applicables sont : Aucune :
"none"
Translations :
"translate(12px, 50 %)" ;
"translate3d(12px, 50 %, 3em)" ;
"translateX(2em)" ;
"translateY(3in)" ;
"translateZ(2px)" ;
Facteur d’échelle sur la taille
"scale(2, 0.5)" ;
"scale3d(2.5, 1.2, 0.3)" ;
"scaleX(2)" ;
"scaleY(0.5)" ;
"scaleZ(0.3)" ;
Déformation en trapèze :
"skew(30deg, 20deg)" ;
"skewX(30deg)" ;
"skewY(1.07rad)" ;
Rotation :
"rotate(0.5turn)" ;
"rotate3d(1, 2, 3, 10deg)" ; //u,v,w,ang
"rotateX(10deg)" ;
"rotateY(10deg)" ;
"rotateZ(10deg)" ;
Perspective (distance entre l’usager et le plan z = 0) "perspective(17px)" ; // ??
Déplacement 2D
"matrix(a11, a21, a12, a22, Tx, Ty)" ;
Déplacement 3D :
"matrix3d(a11, a21, a31, kx, a12, a22, a32, ky, a13, a23, a33, kz, Tx, Ty, Tz, K)" ;
Programme exemple standard (HTML + CSS + Javascript) :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta http-equiv="content-type" content="text/html ; charset=UTF-8"/> <title>Exemple transforamtion</title>
</head>
<style>
main {
width : 400px ;
height : 200px ;
padding : 50px ;
background-image : linear-gradient(135deg, white, cyan, white);
}
#example-element {
width : 100px ;
height : 100px ;
transform-style : preserve-3d ;
transition : transform 1.5s ;
transform : rotate3d(1, 1, 1, 30deg);
}
.face {
display : flex ;
align-items : center ;
justify-content : center ;
width : 100 %;
height : 100 %;
position : absolute ;
backface-visibility : inherit ;
font-size : 60px ;
color : #fff ;
}
.front {
background : rgb(90 90 90 / 70 %);
transform : translateZ(50px);
}
.back {
background : rgb(0 210 0 / 70 %);
transform : rotateY(180deg) translateZ(50px);
}
.right {
background : rgb(210 0 0 / 70 %);
transform : rotateY(90deg) translateZ(50px);
}
.left {
background : rgb(0 0 210 / 70 %);
transform : rotateY(-90deg) translateZ(50px);
}
.top {
background : rgb(210 210 0 / 70 %);
transform : rotateX(90deg) translateZ(50px);
}
.bottom {
background : rgb(210 0 210 / 70 %);
transform : rotateX(-90deg) translateZ(50px);
}
.select-form {
margin-top : 50px ;
}
</style>
<body>
<main>
<section id="example-element">
<div class="face front">1</div>
<div class="face back">2</div>
<div class="face right">3</div>
<div class="face left">4</div>
<div class="face top">5</div>
<div class="face bottom">6</div>
</section>
<div class="select-form">
<label for="transfunction">Select a transform function</label> <select id="transfunction">
<option selected>Choose a function</option> <option>rotate(360deg)</option>
<option>rotateX(360deg)</option>
<option>rotateY(360deg)</option>
<option>rotateZ(360deg)</option>
<option>rotate3d(1, 1, 1, 90deg)</option> <option>scale(1.5)</option>
<option>scaleX(1.5)</option>
<option>scaleY(1.5)</option>
<option>scaleZ(1.5)</option>
<option>scale3d(1, 1.5, 1.5)</option>
<option>skew(17deg, 13deg)</option>
<option>skewX(17deg)</option>
<option>skewY(17deg)</option>
<option>translate(100px, 100px)</option> <option>translateX(100px)</option>
<option>translateY(100px)</option>
<option>translateZ(100px)</option>
<option>translate3d(50px, 50px, 50px)</option> <option>perspective(200px)</option>
<option>matrix(1, 2, -1, 1, 80, 80)</option> <option>matrix3d(1,0,0,0,0,1,3,0,0,0,1,0,50,100,0,1.1)</option> </select>
</div>
</main>
<script>
const selectElem = document.querySelector("select"); const example = document.querySelector("#example-element");
selectElem.addEventListener("change", () => { if (selectElem.value === "Choose a function") { return ;
} else {
example.style.transform = `rotate3d(1, 1, 1, 30deg) ${selectElem.value}`; setTimeout(() => {
example.style.transform = "rotate3d(1, 1, 1, 30deg)" ; }, 2000);
}
});
</script>
</body>
</html>
Traduction en PUR JAVASCRIPT :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta http-equiv="content-type" content="text/html ; charset=UTF-8"/> <title>Exemple transforamtion</title>
</head>
<body></body>
<script>
// La page container
main = document.createElement(’main’);
main.style.cssText = "width : 400px ;height : 200px ;padding : 50px ;"+ "background-image : linear-gradient(135deg, white, cyan, white" ; document.body.appendChild(main);
// Le cube
cube = document.createElement(’section’); cube.style="width :100px ;height :100px ;transform-style :preserve-3d ;"+ "transition :transform 1.5s ;transform :rotate3d(1,1,1,30deg);" ; main.appendChild(cube);
// Couleur des 6 faces
cfaces = ["rgb(90 90 90 / 70 %)",
"rgb(0 210 0 / 70 %)",
"rgb(210 0 0 / 70 %)",
"rgb(0 0 210 / 70 %)",
"rgb(210 210 0 / 70 %)",
"rgb(210 0 210 / 70 %)"];
// transformation qui génère les 6 faces à partir de .…
tfaces = [ "translateZ(50px)",
"rotateY(180deg) translateZ(50px)",
"rotateY(90deg) translateZ(50px)",
"rotateY(-90deg) translateZ(50px)",
"rotateX(90deg) translateZ(50px)",
"rotateX(-90deg) translateZ(50px)"];
// Génération des div qui représentent les 6 faces. For (i = 0 ; i < 6 ; i++)
{
div = document.createElement(’div’);
div.style.display="flex" ;
div.style.alignItems="center" ;
div.style.justifyContent="center" ;
div.style.width="100 %" ;
div.style.height="100 %" ;
div.style.position="absolute" ;
div.style.backfaceVisibility="inherit" ; div.style.fontSize="60px" ;
div.style.color="#fff" ;
div.textContent = `${i+1}`;
div.style.background = cfaces[i];
div.style.transform = tfaces[i];
cube.appendChild(div);
}
// Selecteur des transformations dans un div avec un label selectElem = document.createElement(’select’); dive = document.createElement(’div’);
dive.style="margin-top : 50px" ;
labtxt = document.createElement(’label’); labtxt.textContent = "Select a transform function" ; // labtxt.for = selectElem ; // ça marche mais est-ce que ça sert ? dive.appendChild(labtxt);
dive.appendChild(selectElem);
main.appendChild(dive);
// Les options du sélecteur
options = ["Choose a function",
"rotate(360deg)",
"rotateX(360deg)",
"rotateY(360deg)",
"rotateZ(360deg)",
"rotate3d(1, 1, 1, 90deg)",
"scale(1.5)",
"scaleX(1.5)",
"scaleY(1.5)",
"scaleZ(1.5)",
"scale3d(1, 1.5, 1.5)",
"skew(17deg, 13deg)",
"skewX(17deg)",
"skewY(17deg)",
"translate(100px, 100px)",
"translateX(100px)",
"translateY(100px)",
"translateZ(100px)",
"translate3d(50px, 50px, 50px)",
"perspective(200px)",
"matrix(1, 2, -1, 1, 80, 80)",
"matrix3d(1,0,0,0,0,1,3,0,0,0,1,0,50,100,0,1.1)"]; // Ajout des options au sélecteur
for (i=0 ; i < options.length ;i++)
{
opt = document.createElement(’option’); opt.textContent = options[i];
selectElem.appendChild(opt);
}
// Appel de la callback quand changement de sélection selectElem.addEventListener("change", onElemChange); // La callback
function onElemChange()
{
if (selectElem.value === "Choose a function") return ; else {
cube.style.transform = `rotate3d(1, 1, 1, 30deg) ${selectElem.value}`; setTimeout(move, 2000); // Appel move après un délai de 2 secondes
}
function move() // Retour à la transformation origine { cube.style.transform = "rotate3d(1, 1, 1, 30deg)" ;}
}
</script>
</html>
Voici quelques couleurs prédéfinies :
1.Basic Colors:
•red
•blue
•green
•yellow
•black
•white
•gray
•purple
•orange
•pink
2.Extended Colors:
•aqua
•fuchsia
•lime
•maroon
•navy
•olive
•silver
•teal
3.Shades and Tints:
•lightblue
•lightgreen
•lightgray
•darkblue
•darkgreen
•darkgray
Pratiquement tous les éléments peuvent contenir du texte qui leur est affecté à l’aide de l’attribut textContent. L’aspect de ce texte peut être géré par de nombreux attributs de style :
•font-style : l’aspect général qui est fixé par un des mots clés suivants : normal | italic | oblique | oblique 40deg. Dans ce dernier cas l’inclinaison est précisée en degrés.
•Font-variant : une variante de l’aspect qui peut être : normal | small-caps
•font-weight : L’épaisseur peut-être : normal | bold | lighter | border | 450 (c’est une valeur intermédiaire de 0 à 1000 ?)
•font-size : La taille que l’on peut exprimer comme : 1.2em | 12px | xx-small | x-small | small | medium | large | xlarge | x-large |xx-large | xxx-large | math |...
•line-height : l’interligne
•font-stretch : Possibilité d’expansion : condensed | expanded | ultra expanded
•font-family : La famille. Il en existe énormément.
Parmi les familles de police (font-family) on distingue les polices avec ou sans empattements (serif) et les polices à chasse fixe (monospace). En voici quelques unes :
Polices à empattements (serif) :
"Times New Roman" | Times | "Liberation Serif" | FreeSerif | serif | Georgia | "DejaVu Serif" | Norasi
Polices sans empattements (sans-serif) :
Arial | Helvetica | "Liberation Sans" | FreeSans | "Trebuchet MS" | Arial | Helvetica | "Lucida Sans" | "Lucida Grande" | "Lucida Sans Unicode" | "Luxi Sans" | Tahoma | Geneva | Kalimati | Verdana | "DejaVu Sans" | "Bitstream Vera Sans" | Geneva | Impact | "Arial Black" | sans-serif
Polices à chasse fixe (monospace) :
"Courier New" | Courier | "Liberation Mono" | Monaco | "DejaVu Sans Mono" | "Lucida Console" | "Andale Mono" | monospace
La propriété de style font permet de fixer un ensemble de propriétés, soit à l’aide d’un mot clé-unique sélectionnant une police système, soit à l’aide d’un string combinant plusieurs sous-propriétés
Le style font peut préciser une des polices système prédéfinies suivantes :
caption, icon, menu, message-box, small-caption, status-bar
On peut associer à l’attibut font un string présentant une combinaison des sous-proprietés comme ceci, en html par exemple : <!DOCTYPE html><html><head><meta><meta charset="utf-8"></head>
<body>
<p style="font :italic bold 50px Times">Bonjour Michel</p> </body></html>
ou bien en javascript :
<!DOCTYPE html><html><head><meta><meta charset="utf-8"></head><body><script> var p = document.createElement(’p’);
document.body.appendChild(p);
p.style.font = "italic bold 50px Times" ; p.textContent = "Bonjour Michel"
</script></body></html>
Les règles suivantes s’appliquent pour construire le string affecté à font : la présence de size et family est obligatoire,
•size doit être après style, variant et weight
•si height est utilisé, il doit être accolé après size et séparé par un /, comme ceci size/height
•family doit figurer en dernier
en gros, on a :
[style] [variant] [weight] [stretch] size [/height] family
Une action par défaut est parfois générée par le navigateur (par exemple l’ouverture d’un menu contextuel lors d’un clic droit).
Quel que soit l’événement et l’action par défaut, on peut empêcher son appel avec l’instruction event.preventDefault();.
Au niveau du html et du javascript les événements sont nommés par des attribus du type onclick, onload, ... (cas-insensitive) auquels on assigne la callback à exécuter quand cet événement survient.
Voici une liste non exhaustive des noms de principaux événements :
Nom dans addEventListener |
Nom html / javascript |
Description |
DOMContenLoaded |
|
fin de l'analyse de la page et des scripts. Peut-être utilisé pour ajouter du contenu ou excécuter une action quelconque. |
load |
onload |
fin de chargement de la page web |
contextmenu |
oncontextmenu |
clic droit sur un élément |
resize |
|
action sur la partie resize de la fenêtre |
click |
onclick |
clic sur un élément |
dblclick |
ondblclick |
double clic sur un élément |
keydown |
onkeydown |
une touche est appuyée |
keypress |
onkeypress |
une touche est maintenue enfoncée |
keyup |
onkeyup |
une touche est relâchée |
mouseenter |
onmouseenter |
le curseur entre au dessus d'un élément |
mouseleave |
onmouseleave |
le curseur quitte l'élément |
select |
onselect |
sélection d'une option dans un select |
change |
onchange |
changement de valeur sur un select, un checkbox.. |
submit |
onsubmit |
soumission d'un formulaire |
focus |
onfocus |
l'élément reçoit le focus |
blur |
onblur |
l'élément perd le focus |
La plupart de ces événements sont des attributs des objets du DOM. Pour leur affectr une callback, il suffit de l'assigner à l'attribut événement considéré. Exemple :
En html : <button onclick="callbackClick()">Texte bouton</button>
En javascript (en supposant que btn est le nom de la variable associée au bouton :
btn.onclick = callbackClick;
ou bien
btn.addEventListener('click', callbackClick);
La callback callbackChange d'un checkbox
<input id="inMarche" type="checkbox" onchange="callbackChange()"> est appelée lorsqu'on modifie la coche. On obtient sa valeur dans la callback par le biais de elem.checked = true ou false suivant le cas (elem = document.getElementById("inMarche")). On peut modifier la valeur de elem.checked par programmation. cela n'appellera pas la callback. Si on veut l'appeler il faut dispatcher l'évènement : elem.dispatchEvent(new Event('change'));
Cette dernière méthode est surtout utilisée pour affecter une callback à un élément elem lorsqu'il ne possède pas l'attribut event correspondant par défaut, ou bien pour remplacer la callback qui est attribuée par défaut à cet attribut, ce qu'on fait au moyen de la fonction : elem.addEventListener (event, callback);
Exemples :
onclick pour changer image fond
<html><head>
<script>
var ixfond = 4;
var fonds = ['fond0.jpg', 'fond1.jpg', 'fond2.jpg', 'fond3.jpg'];
function swfond()
{
ixfond += 1; if (ixfond == 5) ixfond = 0;
if (ixfond < 4) document.body.style.backgroundImage = "url("+fonds[ixfond]+")";
else document.body.style.backgroundImage = "none";
}
</script>
</head>
<body style="background-size:cover;">
<button onclick="swfond()">Changer de fond</button>
</body></html>
Le style étend la photo pour couvrir toute la largeur. Cela la déforme. En son absence la photo est duplliquée,...
onresize pour afficher la taille
onresize ne concerne que l'élément window. Exemple où on affiche en permanence la dimension de la fenêtre :
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
function getResolution() {
var ecran = document.getElementById("ecran");
ecran.textContent = "Résolution d'écran: " + screen.width + "x" + screen.height + ".";
var wind = document.getElementById("wind");
wind.textContent = "Résolution de fenêtre : " + window.innerWidth + "x" + window.innerHeight + ".";
var page = document.getElementById("page");
page.textContent = "Résolution de page: " + document.documentElement.scrollWidth + "x" + document.documentElement.scrollHeight + ".";
}
window.onresize = getResolution;
</script>
</head>
<body>
<p id="ecran">Résolution d'écran inconnue.</p>
<p id="wind">Résoltion fenêtre inconnue</p>
<p id="page">Résolution page inconnue</p>
<button type="button" onclick="getResolution();">Récupérer les résolutions</button>
</body>
</html>
Ici, on affecte la fonction getResolution pour l'action onresize de l'élément window. On aurait pu utiliser un addEventListener, comme ceci :
//window.onresize = affichSize;
window.addEventListener('resize', affichSize);
Pour limiter la fréquence des appels de l'action, on peut mettre un anti-rebond avec un timeout.
<!DOCTYPE html>
<html><head><meta charset="utf-8"></head>
<body>
<p>Redimensionner la fenêtre du navigateur pour déclencher l'événement resize.</p>
<p>Window size: <span id="taille"></span></p>
<script>
const pswin = document.getElementById('taille');
var idtm = false;
function affichSize() {
clearTimeout(idtm);
idtm = setTimeout(() => {
pswin.textContent = window.innerHeight + "x" + window.innerWidth;
}, 200); // delai de 200 ms avant l'appel de l'action.
}
//window.onresize = affichSize;
window.addEventListener('resize', affichSize);
affichSize(); // Pour le 1er affichage
</script>
</body>
</html>
Les callbacks associées dans le html par des affectations du type :
<button onclick="loadTable()"> sont généralement définies dans la partie <script>...<\script> . Si cette partie est normale (pas de type = module) la callback loadTable peut y être définie n'importe où.
Si on est dans le cas d'un <script type = module>...<\script>, alors la callback ne sera pas disponible dans l'espace global. Dans ce cas on ne peut pas utiliser onclick="loadTable()", on fera : <button id="btnTable"> et on ajoutera un listener dans le code javascript :
document.getElementById("btnTable").addEventListener("click", loadTable); ce qui permettra d'accéder à la callback.
Les propriétés les plus utilisées de l’objet événement sont :
type |
le type de l’évènement (click ; mouseover...etc) |
currentTarget |
l’élément qui a déclenché l’évènement |
target |
l’élément survolé au déclenchement de l’évènement dont le parent est l’élément qui possède l’évènement. |
relatedTarget |
l’élément lié à l’élément qui a déclenché l’événement |
button |
le bouton de la souris qui a été cliqué (0 :gauche ; 1 :centre ; 2 :droite) |
clientX |
la coordonnée horizontale du pointeur de la souris, par rapport à la fenêtre du navigateur. |
clientY |
la coordonnée verticale du pointeur de la souris, par rapport à la fenêtre du navigateur |
pageX |
la coordonnée horizontale du pointeur de la souris, par rapport à la fenêtre du navigateur plus la position de la barre de scroll. |
pageY |
la coordonnée verticale du pointeur de la souris, par rapport à la fenêtre du navigateur plus la position de la barre de scroll. |
screenX |
la coordonnée horizontale du pointeur de la souris, par rapport à l’écran |
screenY |
la coordonnée verticale du pointeur de la souris, par rapport à l’écran |
keyCode |
le code de caractère ASCII de la touche qui a généré l’événement pour onkeydown et onkeyup. |
which |
idem keyCode. |
charCode |
le code Unicode de la touche qui a généré l’événement pour onkeypress. |
key |
la valeur de la touche qui a généré l’événement pour onkeypress onkedown et onkeyup. |
altKey |
oui ou non la touche "ALT" a été pressé quand un événement a été déclenché |
shiftKey |
oui ou non la touche "SHIFT" a été pressé quand un événement a été déclenché |
ctrlKey |
oui ou non la touche "CTRL" a été pressé quand un événement a été déclenché |
metaKey |
oui ou non la touche "meta" a été pressé quand un événement a été déclenché |
Remarque : Dans la callback, si evt est le nom du paramètre qui reçoit l'argument event, evt.currentTarget est l'équivalent du paramètre qui recevrait l'argument this.
Les méthodes de event sont :
preventDefault(): pour empêcher l'action du navigateur par défaut de se produire comme l'envoie d'un formulaire suite au clic du bouton submit lié au formulaire, ou bien lors du traitement d'un onmousedown pour éviter la sélection de texte, ou pour éviter l'ouverture du menu contextuel sur un élément : Exemple :
document.getElementById("idcyan").addEventListener('contextmenu', e => {e.preventDefault();})
Autre exemple :
<canvas id="canvas" onClick="clicGrille(event)"></canvas><script>
var un = 1; canvas.oncontextmenu = function(e)
{ e.preventDefault(); un = 0; clicGrille(e); un = 1; };</script>
Dans cet exemple, pour le clic gauche on exécute la callback clicGrille(e) avec un=1, et on exécute la meme callback pour le clic droit avec un=0.
stopPropagation() : limite l'évènement a l'élément cible afin d'éviter la propagation aux parents afin d'éviter que, pour chaque parents possédant le même évènement, se produise le déclenchement de la fonction associé. A appeler dans la fonction de l'action qui veut éviter la propagtion vers les parents du l'appelant.
Exemple d'utilisation de l'objet event pour la position de la souris :
On capture le mouvement de la souris dans une zone et on affiche les coordonnées dans une autre.
<html>
<head>
<title></title>
<script type="text/javascript">
<!--
function position(ev)
{
var Xfen, Xdoc, Yfen, Ydoc, elem;
Xfen = ev.clientX;
Yfen = ev.clientY;
Xdoc = Xfen + document.body.scrollLeft;
Ydoc = Yfen + document.body.scrollTop;
elem = document.getElementById("idMouse");
elem.innerHTML = " Xdoc= "+Xdoc+" px ; Ydoc= "+Ydoc+" px<br>";
elem.innerHTML+= " Xfen= "+Xfen+" px ; Yfen= "+Yfen+" px";
}
//-->
</script>
</head>
<body>
<!-- Le mouvement de la souris est capturé dans ce paragraphe jaune -->
<p onmousemove="position(event)" style="background-color:yellow">
Paragraphe de capture de la souris<br>
Paragraphe de capture de la souris<br>
Paragraphe de capture de la souris<br>
Paragraphe de capture de la souris<br>
Paragraphe de capture de la souris<br>
</p>
<div id="idMouse">Zone d'affichage des coordonnées :</div>
</body>
</html>
Cet attribut permet d'identifier la source du dernier évènement généré. Ainsi si même seule callback est affectée à plusieurs évènements (clics, frappe, etc.) on peut obtenir le nom ou l'identifiant de l'émetteur. Exemple :
function infos()
{
{ alert('Evènement généré par "' + window.event.srcElement.name
+ '"\n identifiant = ' + window.event.srcElement.id);}
}
La callback, fonction de rappel à exécuter lorsqu’un événement est détecté, parfois appelée écouteur d’événement, est couramment appelée eventListener.
Au niveau des parties codées en html pur, les détecteurs agissent en tant qu’attribut des éléments concernés du DOM. A cet attribut on assigne la fonction de rappel (l’eventListener) à exécuter, comme suit :
<button onclick="effacer()">c</button>
ou
<button onclick="effacer(event)">c</button> Le nom de la callback est suivi de la paire de parenthèses, sans arguments, ou avec éventuellement l’objet événement s’i l’action à réaliser nécessite la connaissance d’un de ses paramètres, ce qui n’est généralement pas le cas pour un bouton. Les guillemets sont facultatifs.
Au niveau du javascript on assigne l’eventListener aux détecteurs onclick, onload, ...de l’élément concerné, mais le nom est mis sans les parenthèses. Ainsi on aura : b.onclick = effacer ;
L’eventListener peut être défini avec l’objet événement en argument, ou sans si sa connaissance n’est pas nécessaire à l’action à programmer. Possibilités :
var effacer = function() {...}
var effacer = function(ev) {...}
function effacer() {...}
function effacer(ev) {...}
Remarque : Lorsqu’une même callback est associée à différents éléments du DOM si la callback prend l’objet événement en argument, il permet de connaître celui qui a produit l’appel : event.target.
Considérons une callback qui n’a pas besoin pas l’objet événement. Il est alors inutile de lui passer en argument function concatene(str) {...}
Activation en html : Pour l’assignation dans le code html, il suffit de mettre l’appel de la callback avec l’argument spécifique : <button onclick="concatene(’bonjour’)">
Activation en javascript :
Par contre dans le javascript, il faut passer par une programmation plus complexe. Il faut créer localement une fonction qui ne fait qu’appeler la callback avec son argument :
b.onclick = function () {concatene(’bonjour’);}; On ne peut pas écrire b.onclick = concatene(’bonjour’); car dans ce cas on réaliserait un appel de la fonction concatene avec pour argument ‘bonjour’ et c’est le retour de cette fonction (qui n’en fait peut-être pas) qui serait affecté au détecteur onclick. On est donc obligé de générer une fonction (anonyme ou non, mais il ne sert à rien de lui donner un nom) qui fera l’appel de la callback avec le bon argument.
Dans ce cas, l’événement est en argument de l’eventListener : function concatene(str, e) { ..} L’ordre des arguments est indifférent, mais il faudra le respecter au niveau de l’activation.
Activation en html :
<button onclick="concatene(’bonjour’,event)">
Activation en javascript :
b.onclick = function (e) {concatene(’bonjour’,e);};
Exemple d’interception du clic droit :
<canvas id="canvas" onClick="clicGrille(event)"></canvas><script> var un = 1 ; canvas.oncontextmenu = function(e) { e.preventDefault(); un = 0 ; clicGrille(e); un = 1 ; };</script> Dans cet exemple, pour le clic gauche on exécute la callback clicGrille(e) avec un=1, et on exécute la même callback pour le clic droit avec un=0.
Nous avons vu comment affecter un eventListener à un détecteur d’événement. Mais cette assignation remplace éventuelle une assignation précédente. Ainsi pour supprimer l’assignation <button onclick="evaluer()">c</button> ou b2.onclick = evaluer ; il suffit de faire :
<button onclick="">c</button> ou b2.onclick = null ;
Il existe également une fonction dédiée à cette suppression : removeEventListener, que l’on utilise comme ceci : b2.removeEventListener(’click’, evaluer);
On remarquera que le type de détecteur onclick perd ici son préfixe on.
Si on désire affecter une ou plusieurs fonctions de rappel à un même détecteur pour un même élément, il existe la fonction spécialisée addEventListener qui réalise cette affectation simple ou multiple. Sa syntaxe est :
b2.addEventListener(’click’, evaluer);
Si on ne fait pas d’autre ajout pour ‘click’ à l’élément b2, c’est strictement équivalent à b2.onclick = evaluer ; Par contre, cela permet d’ajouter une autre fonction de rappel qui sera exécutée après la précédente.
B2.addEventListener(’click’, autreAction); Exemple d’écouteurs
Placer une zone script javascript dans le head avec :
function demarrage() { alert("Hello World !");} window.addEventListener("load",demarrage); // ou window.onload = demarrage ;
ou bien au chargement du document :
document.addEventListener('load', fonction_1, false);
document.addEventListener('load', fonction_2, false);
Réagir à la frappe sur le clavier document.addEventListener(’keydown’, monAction);
...
function monAction(e) {
... if (e.key === "a") {..}
};
L’événement e transmis comporte de très nombreux paramètres en plus de key(voir : KeyboardEvent – Référence Web API | MDN (mozilla.org) )
Soit à écrire une page qui va permettre à l’utilisateur de sélectionner avec un explorateur de fichier, le fichier JavaScript à exécuter :
La page sera très sommaire, on clique sur le bouton choisir un fichier, cela va ouvrir un explorateur de fichier qui permettra de sélectionner le fichier *.js que l’on veut exécuter. Il sera chargé et la page sera remplacée par le contenu généré par le fichier JavaScript.
Voici un première version du programme qui utilise du HTML et du JavaScript (fichier EXE_JS.HTML) :
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body>
<!-- Création d’un input sélecteur de fichier dans le body --> <input type="file" id="myfile" accept=".js"> <script>
// On identifie cet input sélecteur de fichier var selecteur = document.getElementById(’myfile’);
// La callback exécuté lors d’un choix de fichier var recup = function() {
// On crée une zone script JavaScript
var js = document.createElement(’script’); js.type = "text/JavaScript" ;
// dont la source est le fichier choisi js.src = selecteur.files[0].name ;
// on supprime le sélecteur
selecteur.remove();
// on le remplace par le script choisi
document.body.append(js);
}
// La callback recup sera appelée si l’usage choisi un fichier selecteur.onchange = recup ;
</script>
</body>
</html>
Examinons l’action de la callback :
•on crée l’équivalent d’une nouvelle section <script>...</script> pour le nouveau fichier JavaScript identifié par la variable js,
•on spécifie que ce script sera du texte JavaScript (peut-être inutile car c’est l’option par défaut),
•on spécifie le nom du fichier source. Le sélecteur de fichier permet d’en sélectionner plusieurs quand l’attribut multiple est utilisé. Ce qui fait que les fichiers sélectionnés se trouvent dans un tableaux files. Ce tableau a de nombreux attributs (taille, date...), et celui qui nous intéresse est son name.
•On fait le vide dans la page actuelle en effaçant son seul élément : le selecteur.
•On ajoute au document l’élément script que l’on vient de créer.
Essayez ce programme : Lancer EXE_JS.HTML (double clic dans l’explorateur de fichier), puis cliquer sur le bouton Choisir un fichier dans la page Web qui s’ouvre. Un explorateur de fichier s’ouvre pour choisir le fichier. Rechercher et sélectionner le programme coucou_ok.js. Qui contient les instructions suivantes :
var d = document.createElement(’div’);
d.innerHTML = "Bonjour tout le monde !" ;
document.body.appendChild(d);
La première ligne crée une zone div et la nomme d. C’est une zone à tout faire, sans caractéristique particulière. A l’intérieur on peut mettre du texte, des images... à l’aide de l’attribut innerHTML. Ici, avec la deuxième ligne, on y met le texte "Bonjour tout le monde !". Et ce n’est qu’avec la troisème ligne que cette zone, qui pour le moment n’existe qu’en mémoire, est placée dans le corps (body) du document à l’aide de la méthode appendChild.
Attention. L’affection js.src = selecteur.files[0].name ; ne marche que si le fichier js est dans le même répertoire que le fichier html où en dans un sous-répertoire car cette méthode va chercher dans la sous-arborescence du fichier html du serveur. Pour aller chercher ailleurs faut passer par une URL.
La version précédente ne contenait qu’une ligne de HTML dans le body. Celle qui installe le bouton de sélection de fichier. Voici comment la supprimer (fichier EXE_purJS.HTML) :
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body>
<script>
// Création d’un input sélecteur de fichier var selecteur = document.createElement(’input’); selecteur.type = "file" ;
selecteur.accept = ".js" ;
// On l’ajoute à la page
document.body.append(selecteur);
// La callback exécuté lors d’un choix de fichier var recup = function() {
// On crée une zone script JavaScript
var js = document.createElement(’script’); js.type = "text/JavaScript" ;
// dont la source est le fichier choisi js.src = selecteur.files[0].name ;
// on supprime le sélecteur
selecteur.remove();
// on le remplace par le script choisi
document.body.append(js);
}
// La callback recup sera appelée si l’usage choisi un fichier selecteur.onchange = recup ;
</script>
</body>
</html>
On remplace la ligne en question par les 4 lignes suivantes : // Création d’un input sélecteur de fichier var selecteur = document.createElement(’input’); selecteur.type = "file" ;
selecteur.accept = ".js" ;
// On l’ajoute à la page
document.body.append(selecteur);
où on crée un élément d’entrée grâce à l’argument , nommé selecteur. On indique que cet élément est de type "file", c’est-à-dire qu’il permet de sélectionner des fichiers, et on ajoute ce sélecteur à la page (qui était vierge). Le reste est inchangé.
On peut créer un type de détecteur pour des événements personnalisés. La syntaxe suivante : const my_event = new Event(’build’ [, options]); crée un détecteur d’événement nommé ‘build’ et assigné à la variable my_event.
On peut affecter une callback écouteur (un eventListener) de ce type d’événement à l’élément elem du DOM de manière classique par :
elem.addEventListener(’build’, function (e) {..}); et ensuite quand on veut que l’élément du DOM émette l’événement on utilise l’instruction :
elem.dispatchEvent(my_event);
ce qui provoquera l’appel des eventListener de cet événement, s’il y en a.
Pour ajouter des propriétés à l’événement, il existe le constructeur spécial CustomEvent : const my_event = new CustomEvent(’build’, {prop1 : "coucou", prop2 : Date()});
Mise en oeuvre :
Nouvelle méthode :
// Création
const event = new Event('build' [, EventInit]);
// Listener affecté à un lélément elem (nimporte quel, document ou autre...)
elem.addEventListener('build', function (e) { /*// e.target désigne l'elem */ }, false);
// Un élément émet l'événement
elem.dispatchEvent(event);
L'EventInit a pour valeurs par défaut {"bubbles":true, "cancelable":false}
"bubbles":true indique que l'événement peut remonter vers les parents
"cancelable":false indique que l'événement n'est pas cancelable.
Ancienne méthode :
/ Création
const event = document.createEvent('Event');
// Nomination de l'événemnt à 'build'.
event.initEvent('build', true, true);
// Listener pour l'événemnt
elem.addEventListener('build',function (e)
{ /* e.target désigne l'elem */},false);
// La cible peut être n'importe quel élément ou un autre EventTarget.
elem.dispatchEvent(event);
Événement avec des données personnelles : CustomEvent :
// Ajoute un listener à un objet
obj.addEventListener("cat", function(e) { process(e.detail) });
// creation
var event = new CustomEvent("cat", {detail: {hazcheeseburger: true}});
// emission par un objet
obj.dispatchEvent(event);
Le constructeur prend le CustomInit detail pour passer n'importe quelles données qui seront fournies à la callback par le e.detail. En plus du CustomInit on peut spécifier les EventInit vus ci-dessus.
Exemple : Le texte tapé dans un textarea est écouté par un listener de 'input'. La callback émet un customEvent qui prend en detail le contenu du textarea et qui peut remonter aux parents. On ajoute un listener à la form, parente de l'input qui reçoit ces données et les envoie sur la console.
<!DOCTYPE html>
<title>source javascript</title>
</head>
<body>
<form>
<textarea></textarea>
</form>
<script>
const form = document.querySelector('form');
const textarea = document.querySelector('textarea');
// Create a new event, allow bubbling, and provide any data you want to pass
// to the "detail" property
const eventAwesome = new CustomEvent('awesome', {
bubbles: true,
detail: { prise: () => textarea.value }
});
// The form element listens for the custom "awesome" event
// and then consoles the output of the passed text() method
form.addEventListener('awesome', e => console.log(e.detail.prise()));
// As the user types, the textarea inside the form dispatches/triggers
// the event to fire, and uses itself as the starting point
textarea.addEventListener('input', e => e.target.dispatchEvent(eventAwesome));
</script>
</body>
</html>
Même exemple plus compliqué où l'événement est créé anonymement et dynamiquement au moment du dispatch :
<!DOCTYPE html>
<title>source javascript</title>
</head>
<body>
<form>
<textarea></textarea>
</form>
<script>
const form = document.querySelector('form');
const textarea = document.querySelector('textarea');
form.addEventListener('awesome', e => console.log(e.detail.text()));
textarea.addEventListener('input', function() {
// Create and dispatch/trigger an event on the fly
// Note: Optionally, we've also leveraged the "function expression" (instead of the "arrow function expression") so "this" will represent the element
this.dispatchEvent(new CustomEvent('awesome', { bubbles: true, detail: { text: () => textarea.value } }))
});
</script>
</body>
</html>
Autre exemple de simulation d'un clic dans un checkbox, en appuyant sur un bouton. 2 autres boutons permettent d'interdire ou de re-autoriser cette action.
<!DOCTYPE html>
<title>source javascript</title>
</head>
<body>
<input type="checkbox" id="checkbox"/><label for="checkbox">Checkbox</label>
<input type="button" onclick="simulateClick();" value="Simulate click"/>
<input type="button" onclick="addHandler();" value="Add a click handler that calls preventDefault"/>
<input type="button" onclick="removeHandler();" value="Remove the click handler that calls preventDefault"/>
<script>
function preventDef(event) {
event.preventDefault();
}
function addHandler() {
document.getElementById("checkbox").addEventListener("click",
preventDef, false);
}
function removeHandler() {
document.getElementById("checkbox").removeEventListener("click",
preventDef, false);
}
function simulateClick() {
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
var cb = document.getElementById("checkbox");
var canceled = !cb.dispatchEvent(evt);
if(canceled) {
// A handler called preventDefault
alert("canceled");
} else {
// None of the handlers called preventDefault
alert("not canceled");
}
}</script>
</body>
</html>
Le choix dans une liste se fait à l’aide d’un champ <select> Les éléments à choisir sont mis dans la liste à l’aide du champ <option>.
Exemple :
<html><head><body>
<!-- Champs d’affichage des résultats →
<p id="res1"></p>
<p id="res2"></p>
<!-- Instruction permettant un choix dans une liste --> <select id="seldir" onchange="changeFunc()"> <!-- Création de la liste des choix-→
<option hidden>Developpez la sélection></option> <option>1er choix</option>
<option>2me choix</option>
</select>
<script>
function changeFunc() {
var zs = document.getElementById("seldir");
var idx = zs.selectedIndex ;
var str = zs.value ;
document.getElementById("res1").textContent="Choix item numéro : " + idx ; document.getElementById("res2").textContent="Texte : " + str ; zs.selectedIndex = 0 ;
}
</script>
</html>
document.getElementById("seldir").selectedIndex fourni l’index de l’élément sélectionné, MAIS la fonction changeFunc() n’est appelée que si on clique sur une option différente. Elle n’est pas appelée si on clique sur la même option. Si les champs res1 ou res2 sont modifiés par un autre bout de code et qu’on rechoisit la dernière option choisie pour la valeur choisie par l’option, ça ne marchera pas car la callback ne sera pas appelée. Pour qu’elle soit appelée, on peut donner à l’index à l’intérieur de la callback une valeur hors liste, ce qui est fait ici avec la dernière instruction zs.selectedIndex = 0 ;
L’exemple suivant, entièrement en javascript, montre la nécessité de cette instruction : <html><head><meta charset="utf-8"></head><body><script> // Afficheur du résultat
var afficheur = document.createElement("p"); afficheur.innerText = "Texte provisoire" ; document.body.append(afficheur);
// Bouton pour écrire "coucou" dans l’afficheur var bouton = document.createElement("button"); bouton.innerHTML = "Mettre coucou" ;
bouton.onclick = coucou ;
document.body.append(bouton);
// La callback du bouton qui affiche coucou function coucou() {afficheur.innerText = "Coucou" ;}
// Sélecteur de liste pour écrire un jour de la semaine dans l’afficheur var selecteur = document.createElement("select");
selecteur.onchange = changeFunc ;
document.body.append(selecteur);
// Première option chachée : texte "Choisissez un jour" var option = document.createElement("option");
option.textContent = "Choisissez un jour" ; option.hidden = true ; // Non sélectionnable
selecteur.appendChild(option);
// Remplissage des options avec les jours de la semaine
var jours = ["lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche"];
for (i = 0 ; i < jours.length ; i++) {
let option = document.createElement("option");
option.textContent = jours[i];
selecteur.appendChild(option);
}
// La callback d’affichage du jour sélectionné
function changeFunc() {
var str = selecteur.value ;
afficheur.innerText = "Vous avez choisi : " + str ; //selecteur.selectedIndex = 0 ;
}
</script></html>
Si on laisse l’instruction //selecteur.selectedIndex = 0 ; commentée, le sélecteur fonctionne mal : Si on choisit "mardi", l’afficheur affiche mardi, puis si on clique sur le bouton, l’afficheur affiche coucou, puis si on choisit à nouveau "mardi, l’afficheur reste avec coucou. Pour qu’il soit actualisé correctement il faut décommenter l’instruction selecteur.selectedIndex = 0 ;.
<script>
let sel = document.createElement('select'); document.body.append(sel); sel.textContent = "noms"; sel.multiple = true;
sel.onchange = changeFunc;
let noms = ["Michel", "Pierre", "Bernard", "Paul"];
for(let i = 0; i < noms.length; i++) {
let o = document.createElement('option'); sel.appendChild(o); o.textContent = noms[i];
}
function changeFunc()
{
txt.textContent = "Opt selectionnées : "
liste = sel.selectedOptions;
for (let i = 0; i < liste.length; i++) {
txt.textContent += liste[i].label + " ";
}
console.log(txt.textContent)
}
txt = document.createElement('p'); document.body.append(txt);
</script>
ou pour accéder aux index des éléments sélectionnés :
<script>
let sel = document.createElement('select'); document.body.append(sel); sel.textContent = "noms"; sel.multiple = true;
sel.onchange = changeFunc;
let noms = ["Michel", "Pierre", "Bernard", "Paul"];
let opt = [];
for(let i = 0; i < noms.length; i++) {
let o = document.createElement('option'); sel.appendChild(o); o.textContent = noms[i];
opt[i] = o;
}
function changeFunc()
{
txt.textContent = "Opt selectionnées : "
for (let i = 0; i < noms.length; i++)
{
if (opt[i].selected) txt.textContent += i+":"+noms[i]+" ";
}
console.log(txt.textContent)
}
txt = document.createElement('p'); document.body.append(txt);
</script>
Pour simuler des onglets on peut créer un bandeau de boutons comme ceci :
const bandeau = document.createElement('p'); document.body.append(bandeau);
et en dessous une division conteneur :
const conteneur = document.createElement('div'); document.body.append(conteneur);
qui contiendra le contenu de l'onglet appelé par un clic sur son bouton d'activation. Ci-après un exemple avec 3 onglets dont les éléments racines sont assignés aux variables javascript pA, divB et divC. Pour la commodité de la programmation, on les a rangé dans un objet onglets :
const onglets = {
"Onglet A": pA,
"Onglet B": divB,
"Onglet C": divC
};
Voici par l'élément divC :
const divC = document.createElement('div');
divC.style.background = '#f0f0f0';
divC.style.padding = '10px';
divC.innerHTML = "<button>Bouton stylisé</button>";
let p1 = document.createElement('p'); p1.textContent = "Hello la compagnie.";
let p2 = document.createElement('p'); p2.textContent = "Hello EveryBody";
divC.appendChild(p1); divC.appendChild(p2);
qui pour le moment n'est contenu par rien. Et voici les 2 autres éléments, très simples :
const pA = document.createElement('p');
pA.innerHTML = "Contenu <em>HTML</em> quelconque...";
const divB = document.createElement('div');
divB.textContent = "Texte simple sans balises";
La création des boutons du bandeau, leur mise en place et la création de la callback affectant le contenu au conteneur est effectuée de manière dynamique comme suit :
let bouton1 = null; // Pour stocker le premier bouton
for (const nomOnglet in onglets) {
const bouton = document.createElement('button'); // création bouton
bouton.textContent = nomOnglet; // avec son nom
bouton.addEventListener('click', function () // et sa callback
{
conteneur.innerHTML = ''; // Vide le contenu puis ajoute l'élément
conteneur.appendChild(onglets[nomOnglet]);
}
);
bandeau.appendChild(bouton);
if (!bouton1) bouton1 = bouton; if (!bouton1) bouton1 = bouton;
}
onglets[nomOnglet] contient soit pA, soit divB, soit divC. qui ne sont utilisés qu'ici. Dans le cas où ils seraient utilisés ailleurs, il faudrait utiliser un clone comme ceci :
conteneur.appendChild(onglets[nomOnglet].cloneNode(true));
à la place de : conteneur.appendChild(onglets[nomOnglet]);
La variable qui mémorise le bouton du premier onglet est créée uniquement pour faciliter le remplissage initial du conteneur qui est effectué en simulant son click :
if (bouton1) bouton1.click();
Voici une version plus moderne de la boucle de création des boutons :
for (const [name, element] of Object.entries(onglets)) {
const btn = document.createElement('button');
btn.textContent = name;
btn.onclick = () => { conteneur.innerHTML = ''; conteneur.append(element);};
bandeau.append(btn);
if (!bouton1) bouton1 = btn;
}
et une autre version moderne :
Object.keys(onglets).forEach(k => {
const btn = document.createElement('button');
btn.textContent = k;
btn.onclick = () => {conteneur.innerHTML = ''; conteneur.append(onglets[k]);}
bandeau.append(btn);
if (!bouton1) bouton1 = btn;
});
et ci-après une version compacte de if (!bouton1) bouton1 = bouton;
bouton1?.click();
Voici le code complet de la dernière version :
<script>
// La ligne des boutons d'activation des onglets
const bandeau = document.createElement('p'); document.body.append(bandeau);
// La division qui contiendra l'onglet affiché
const conteneur = document.createElement('div'); document.body.append(conteneur);
// Trois contenus bidons pour l'exemple
// Contenu onglet A
const pA = document.createElement('p');
pA.innerHTML = "Contenu <em>HTML</em> quelconque...";
// Contenu onglet B
const divB = document.createElement('div');
divB.textContent = "Texte simple sans balises";
// Contenu onglet C
const divC = document.createElement('div');
divC.style.background = '#f0f0f0';
divC.style.padding = '10px';
divC.innerHTML = "<button>Bouton stylisé</button>";
let p1 = document.createElement('p'); p1.textContent = "Hello la compagnie."; divC.appendChild(p1);
let p2 = document.createElement('p'); p2.textContent = "Hello EveryBody"; divC.appendChild(p2);
// tableau des noms et contenus des onglets
const onglets = {
"Onglet A": pA,
"Onglet B": divB,
"Onglet C": divC
};
// Création dynamique des boutons d'activation et callback d'affichage
let bouton1 = null; // Pour stocker le premier bouton
Object.keys(onglets).forEach(k => {
const btn = document.createElement('button');
btn.textContent = k;
btn.onclick = () => {conteneur.innerHTML = '';
conteneur.append(onglets[k]);}
bandeau.append(btn);
if (!bouton1) bouton1 = btn;
});
bouton1?.click();
</script>
La fonction eval() est extrêment puissante car elle interprête le string qui lui est fournit en utilisant l'interpréteur javascript. Ainsi on peut l'utiliser pour faire une console de commande javascript comme suit :
<body>
<!-- Textarea conversationnel et bouton d'exécution -->
<textarea id="codeInput" rows="4" cols="50" placeholder="Instructions JS ici..."></textarea>
<br><button onclick="interpreterCode()">Exécuter</button>
<script>
// Une fonction qcq que l'on appellera dans l'interptréteur
function somme(a, b) {return a + b;}
// La callback appelée par un click sur le bouton "Exécuter"
function interpreterCode() {
const input = document.getElementById("codeInput");
try {
const result = eval(input.value); // Interprétation du contenu !!!!!!!!!!!!!!!
input.value += "\n=> " + result; // Ajout du résultat à la suite
} catch (error) {input.value += "\nErreur: " + error;}
}
</script>
</body>
Le SVG désigné par la balise <SVG> est un élément du DOM dans lequel on peut dessiner.
Exemple :
<!DOCTYPE html>
<html><head><title>SVG</title></head>
<body>
<svg width="400" height="400" viewBox="0 0 400 400"> <!-- Tracé d’un cercle, trait noir, épaisseur 2 --> <circle cx="200" cy="200" r="100" stroke="black" stroke-width="2" fill="none"/>
<!-- Tracé d’un rectangle, trait bleu, épaisseur 2, remplissage jaune --> <rect x="200" y="200" width="20" height="50" stroke="blue" stroke-width="2" fill="black"/>
<!-- Tracé d’un disque couleur rouge --> <circle cx="200" cy="300" r="25" fill="red"/>
<!-- Tracé d’un arc de cercle, trait vert, latgeur 2 --> <path d="M 60 180 A 100 100 0 0 1 340 180" stroke="green" stroke-width="2" fill="none"/> <!-- Arc de B --> </svg>
</body>
</html>
Les balises circle et rect sont évidentes.
La balise path est très puissante. La propriété d="…" transmet des commandes de description de la courbe qui contient par exemple : M 60 180 pour MoveTo(60,80) m 10 30 pour MoveRel(10,30) L 60 180 pour LineTo(60,80)
l 60 180 pour LineRel(60,80)
H et h pour une ligne horizontale
V et v pour une ligne verticale
Z ou z pour définir une ligne jusqu’au point de départ du path.
Arcs
A rx ry xaxeAngle flagbig flagsweep xend yend
a rx ry xaxeAngle flagbig flagsweep xend yend
Ellipse de rayons rx et ry (utilisés uniquement pour la valeur de leur rapport), allant du point de départ courant au point final xend et yend , l’axe de x fait l’angle xaxeAngle avec l’horizontale, le grand ou le petit arc sera dessiné suivant la valeur de flagbig (1 ou 0) et flagsweep vaut 1 ou 0 pour sens positif ou négatif du dessin. PAS EVIDENT A MAITRISER.
Courbe de Bézier
C x1 y1, x2 y2, x y
c dx1, dy1, dx2 dy2, dx ,dy pour définr une courbe de Bézier du point de départ courant, au point final (x,y), les point 1 et 2 étant les points de contrôle.
Le canevas désigné par la balise <canvas> est un élément du DOM dans lequel on peut dessiner.
Exemple de création en HTML par : <canvas id="idCanv" width="200" height="200"></canvas>
Accès à l’objet Context permettant de dessiner :
var canv = document.getElementById("idCanv"); var ctx = canv.getContext("2d");
Le contexte "2d" correspond aux tracés en coordonnées x,y. Il y a aussi un contexte "3d" expérimental pour les tracés en coordonnées x,y,z.
L’origine est le coin TOP, LEFT. Les y positifs vont vers le bas.
Effacer le canevas (efface le contenu d’un rectangle) : ctx.clearRect(0, 0, canv.width, canv.height);
Tracer un rectangle plein dans le canvas : arguments : coin top, left, puis dimensions w, h :
ctx.fillRect(xleft, ytop, largeur, hauteur);
Définir la couleur de remplissage des tracés postérieurs : ctx.fillStyle = "Red" ;
Tracer le pourtour d’un rectangle : ctx.strokeRect(10, 10, 100, 20);
Définir l’épaisseur des lignes postérieures :
ctx.lineWidth = 4 ; // Moitié de part et d’autre de l’axe du trait
Définir la couleur des lignes :
ctx.strokeStyle = "green" ;
Tracer une ligne brisée (succession de segments) : ctx.beginPath(); // Début définition ligne brisée ctx.moveTo(110, 110);
ctx.lineTo(160, 160);
ctx.lineTo(160, 110);
ctx.lineTo(110, 160);
ctx.moveTo(110, 110);
ctx.lineTo(160, 110);
ctx.stroke(); // tracé de la ligne
tracée entièrement avec la dernière épaisseur et couleur définies avant l’appel de stroke().
Remplissage ligne brisée :
ctx.fill(); // Remplissage du polygone
Le remplissage est effectué avec la couleur fillStyle.
Si fill() est appelé après stroke() sa couleur prend le dessus sur celle des lignes et inversement.
L’un peut avoir plus de segments que l’autre si ces segments sont placés entre les deux.
Dessiner un arc de cercle (entre beginPath() et stroke() et/ou fill()) :
ctx.arc(x0, y0, rayon, angDeb, angFin, bDirect);
•x0 et y0 : coordonnées du centre,
•angDeb et angFin : les angles de début et fin du tracé de l'arc. L'origine des arcs est le point le plus à droite, de coordonnées(x0+r, y0) et les angles sont comptés positifs dans le sens horaire (en descendant de l'est vers le sud).
•Il y a 2 manières de joindre l’angles angDeb à l'angle angFin : par défaut bDirect vaut false et le tracé va de angDeb à angFin dans le sens horaire, sinon avec true, il y va dans le sens direct antihoraire.
Attention :
•Les rectangles sont définis par leur coin top, left alors que les arcs sont définis par le centre, contrairement a QT qui les définit, comme les rectangles par le coin top,left puis par l'angle de départ et la longueur de l'arc
•Il existe un arcTo qui trace une courbe de Bezier entre 2 couples (point, tangente).
NE PAS OUBLIER D’ENCADRER arc PAR beginPath() ET stroke pour tracer le contour ou fill pour le remplir de la couleur précédemment choisie.
// Fonction pour dessiner un cercle
function drawCircle(x, y, r, color) {
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.strokeStyle = color ;
ctx.stroke();
}
// Fonction pour dessiner un disque
function drawDisc(x, y, r, color) {
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.fillStyle = color ;
ctx.fill();
}
On écrit du texte dans le canevas avec la fonction fillText en précisant le string et les coordonnées x,y du point d’ancrage du rectangle encadrant le texte : ctx.fillText("Bonjour tout le monde !", 50, 50);
On précise le coin du rectangle choisi comme ancrage avec les fonctions textBaseline et textAlign : ctx.textAlign = "right" ; //ou "left" ou "center" ou "end" ou "start" (par défaut) en largeur.
Ctx.textBaseline = "top" ; //ou "middle" ou "bottom" (presque défaut) ou "alphabetic" (défaut) en hauteur
On précise le formatage avec la fonction font qui prend en argument un string précisant l’épaisseur, la taille et la famille, avec les mêmes définitions, qui vaut par défaut "10px sans-serif".
Ctx.font = "24px Comic Sans MS" ;
Voir paragraphe 16.8.1.2 comment constituer le string assigné à font.
Exemple de fonction :
/* Dessin d’un texte le long d’un arc de courbe
• ox, oy, radiux : centre et rayon du cercle
• centerArg : angle/Ox du caractère central du string
•La taille du texte dépend du contexte, à savoir :
•ctx.font = "12px Arial" ; // par exemple
*/
function drawArcTexte(ctx, text, ox, oy, radius, centerAng)
{
ctx.save(); // Sauvegarder l’état actuel du contexte ctx.textAlign = "center" ; // Les caractères sont centrés en x nbc = text.length ; // Nombre de caractères pixLength = ctx.measureText(text).width ; // Longueur du texte en pixels ai = (pixLength/nbc)/radius ; // angle occupé par un caractère adeb = -centerAng – nbc*ai/2 ; // angle 1er carcatere ctx.rotate(adeb+Math.PI/2);
// Dessiner chaque lettre sur l’arc
for (i = 0 ; i < nbc ; i++)
{
ctx.fillText(text[i], 0, -radius); // Dessiner la lettre ctx.rotate(ai);
}
ctx.restore(); // Restaurer l’état du contexte
}
Var texte = "Bonjour Tout Le Monde" ; // Texte à afficher var radius = 50 ;
centerAng = Math.PI/6 ;
drawArcTexte(ctx, texte, 100, 100, radius, centerAng); Contexte (save, restore, transform…) Les tracés graphique se font dans un contexte qui comprend l’origine, l’orientation,, les échelles définies par les méthodes transform, rotate,…, la région de clipping, le type de trait ; les polices … La méthode :
ctx.setTransform(1,0,0,1,ox,oy);
ne fait que déplacer l’origine en ox, oy.
La méthode ctx.transform(a,b,c,d,ox,oy) multiplie toute la matrice homogène du contexte, alors que la méthode ctx.setTransform(a,b,c,d,ox,oy) impose cette nouvelle matrice homogène au contexte. Ainsi ctx.setTransform(1,0,0,1,0,0) permet de revenir au contexte initial.
Plus généralement la transformation définie par setTransform(a,b,c,d,ox,oy) effectue la transformation suivante :
X = a x + c y + ox
Y = b x + d y + oy
Versions abrégées :
ctx.rotate(angle);
multiplie la matrice de rotation de la matrice homogène du contexte. L’angle est en radians. Le sens positif correspond ici au sens horaire étant donné que pour le repère origine y est vers le bas (idem rotate de QT qui bizarrement était dans le sens horaire).
ctx.translate(dx, dy);
ajoute la translation à la matrice homogène du contexte.
ctx.save(); sauvegarde le contexte courant
ctx.restore(); restore le contexte sauvegardé
Pour animer un canevas on peut appeler une callback animer() qui efface la scène puis la redessine.
Function animer() {
// Effacer ancienne scene
effacerScene();
dessinerScene();
}
// Lancer l’animation
setInterval(animer, 100);
Dans la fonction effacerScene ou pourra n’effacer que le contenu d’un rectangle avec la fonction : canvas.clearRect(...)
ou bien on peut n’effacer que le contenu d’une forme en la remplissant de vide. Par exemple pour effacer un disque de centre x,y et de rayon r on fera :
ctx.save(); // Sauvegarder le contexte avant d’appliquer la composition ctx.globalCompositeOperation = ‘destination-out’ ; ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fill();
ctx.restore(); // Restaurer le contexte
Ici on remplace le tracé habituel qui utilise la composition par défaut ‘source-over’ qui trace par dessus l’ancien contenu par la composition ‘destination-out’ qui conserve l’ancien contenu là où il ne chevauche pas le nouveau. On remplit le disque de vide ce qui permet d’effacer son intérieur sans toucher l’extérieur, et ensuite on peut redessiner cet intérieur.
Exemple de réajustement d’un dessin en déplaçant un des points à la souris.
<!DOCTYPE html>
<html>
<head>
<title>Canvas interactif</title>
</head>
<body>
<canvas id="napoleonCanvas" width="400" height="400"></canvas> <script>
let canvas = document.getElementById("napoleonCanvas"); let ctx = canvas.getContext("2d");
// Centre et rayon du cercle initial
let cx = 200, cy = 200, r = 100 ;
// Points A, B, C
let points = [
{ x : 130, y : 230, label : "A" },
{ x : 270, y : 230, label : "B" },
{ x : 200, y : 120, label : "C" }
];
let draggedPoint = null ;
// Dessiner la figure
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Dessiner les arcs et leurs labels
drawCircle(points[0].x, points[0].y, distance(points[0], points[1]), "blue", "Arc A");
drawCircle(points[1].x, points[1].y, distance(points[1], points[2]), "green", "Arc B");
drawCircle(points[2].x, points[2].y, distance(points[2], points[0]), "purple", "Arc C");
// Dessiner les points et leurs labels points.forEach(point => drawPoint(point, "red"));
}
// Fonction pour dessiner un cercle
function drawCircle(x, y, r, color, label) { ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.strokeStyle = color ;
ctx.stroke();
// Afficher le label du cercle
ctx.fillStyle = color ;
ctx.font = "16px Arial" ;
ctx.fillText(label, x + r * 0.7, y – r * 0.7);
}
// Fonction pour dessiner un point et son label function drawPoint(point, color) {
ctx.beginPath();
ctx.arc(point.x, point.y, 5, 0, 2 * Math.PI); ctx.fillStyle = color ;
ctx.fill();
// Afficher la lettre du point
ctx.fillStyle = "black" ;
ctx.font = "16px Arial" ;
ctx.fillText(point.label, point.x + 8, point.y – 8);
}
// Fonction pour calculer la distance entre deux points function distance(p1, p2) {
return Math.hypot(p2.x – p1.x, p2.y – p1.y);
}
// Gestion du MOUSEDOWN : Exam si clic au voisinage d’un point : si distance // clic à un point < 10 renvoie le draggedPoint.
Canvas.addEventListener("mousedown", function (event) { let mouseX = event.offsetX ;
let mouseY = event.offsetY ;
points.forEach(point => {
if (Math.hypot(mouseX – point.x, mouseY – point.y) < 10) { draggedPoint = point ;
}
});
});
// Gestion du MOUSEMOVE : Si draggedPoint != null déplace le point et redessine la figure
canvas.addEventListener("mousemove", function (event) { if (draggedPoint) {
draggedPoint.x = event.offsetX ;
draggedPoint.y = event.offsetY ;
draw();
}
});
// Gestion du MOUSEUP : Si draggedPoint != null raz le draggedPoint canvas.addEventListener("mouseup", function () { if (draggedPoint) {
draggedPoint = null ;
}
});
// Dessiner la figure au chargement
draw();
</script>
</body>
</html>
L’api canvas peut utiliser des images de type CanvasImageSource qui englobe les types suivants : HTMLImageElement crées avec le constructeur Image() , SVGImageElement qui sont incorporées avec l’élément HTML <image> HTMLVideoElement qui sont incorporées avec l’élément HTML <video> HTMLCanvasElement, un autre élément du <canvas> Une image déjà présente dans la page peut être référencée : dans la collection document.Images(),
•par document.getElementsByTagName() ;
•par document.getElementsById() ;
Une image sera chargée dans une balise <img>
<img id=’idImg’ src="treasuremap.png" style=’display :none’> mais pas montrée (style=’display :none’).
On désire dessiner une partie de cette image dans le canevas déclaré par : <canvas id="c1" width="400" height="400"></canvas> Dans le javascript, on peut procéder comme suit :
let imgsrc = document.getElementById(’idImg’); let ctx = document.getElementById(’c1’).getContext(’2d’); imgsrc.addEventListener(’load’, function(){ctx.drawImage(imgsrc, 0, 0);}); L’image sera plaquée dans le canevas dès que le navigateur aura fini de la charger.
Pour les images qui ne sont pas déjà présentes dans la page, une image peut être sélectionnée par l’utilisateur à l’aide d’un input comme celui-ci :
<input type="file" id="inImg"/>
puis chargée et plaquée dans le canvas comme suit : document.getElementById(’inImg’).addEventListener(’change’, plaqueImg, false); function plaqueImg(e) {
let ctx = document.getElementById(’canvas’).getContext(’2d’);
let img = new Image();
img.onload = function()
{
ctx.drawImage(img, 0, 0); };
img.src = (webkitURL||URL).createObjectURL(e.target.files[0]); // éventuellement
img.onload = function(){(webkitURL||URL).revokeObjectURL(this.src);}
}
}
•La callback onload doit être précisée avant l’appel de l’assigtion de la proropriété src. Pourquoi ? Peut-être que si on fait l’assignation avant , img.src reçoit un objet null et quand la callback s’exécute elle le fait avec cet img qui n’e contient pas de données.
•On peut omettre l’événement e en argument de function plaqueImg(e) {..}
•==> function plaqueImg() {..} et en remplaçant e.target.files par this.files, car this est bien défini (on n’est pas dans une arrow function)
Arguments de drawImage :
La méthode drawImage du contexte du canevas peut prendre 3, 7 ou 9 arguments. On a les cas suivants :
•drawImage (img, left, top)
•ctx.drawImage (img, left , top, largeur, hauteur).
•DrawImage(img, srcX, srcY, srcLargeur, srcHauteur, left, top, destLargeur, destHauteur);
Pour les arguments :
•top et left sont les coordonnées de destination dans le canvas du coin gauche-haut de l’image, largeur et hauteur sont les dimensions attribuées à l’image (qui est réduite ou étendue à ces dimension là
•srcX, srcY, srcLargeur, srcHauteur définissent la partie de l’image à extraire et left, top, srcLargeur, srcHauteur définissent la zone où la partie de l’image sera adaptée (en réduction ou étorement ou les deux).
Pour placer le tracé d’un canevas dans une image on le convertit d’abord en objet data-URL comme ceci :
du = canvas.toDataURL();
La méthode prend 1 ou 2 arguments pour spécifier le type de data. Par défaut c’est "image/png". On peut aussi préciser canvas.toDataURL("image/jpeg", 1.0); // qualité max ou canvas.toDataURL("image/jpeg", 0.5); // qualité moyenne
Ensuite on peut affecter cet objet à une image. Par exemple : image = new Image();
image.src = du ;
ou bien directement
image = new Image();
image.src = canvas.toDataURL("image/png");
Cet objet image peut ensuite être affiché dans le document (append ou appenchild) ou sauvegardé.
Pour travailler avec des portions de canevas comme des images, il est plus simple d’utiliser les fonctions suivantes :
imgData = ctx.getImageData(0, 0, wc, hc); // extraction d’une partie puis :
ctx.putImageData(imgData, x, y, srcx, srcy, W, H); // placage en position x,y de lapartie extraite de la position srcx, srcy de dimensions WxH
On peut également utiliser canvas.toBlob(callback, ‘typeMime’); pour enregistrer le canvas sur disque.
On peut faire des dessins 3D avec la libraire three.js que l’on importe comme suit : <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
Voir des exemples ici : D :\MesProgs_Langages\PAGES-WEB\JavaScript\2D-3D-chatgpt.
Les données manipulées et transférées sont classées selon un certain nombre de types standards appelés les types MIME (Multipurpose Internet Mail Extensions) et dont le nom est composé en suivant la syntaxe type/sous-type. Les plus courants sont les suivants :
•text/plain : type MIME par défaut pour les fichiers de texte.
•application/octet-stream : type MIME par défaut dans tous les autres cas et en particulier pour les fichiers de type inconnu.
•text/html : fichier html (texte)
•application/json : données codées en json (texte)
•application/javscript : application codée en javascript (texte).
•image/jpeg : fichier image jpeg
•image/png : fichier image png
•audio/ ??? : fichier audio codé mpeg, ogg…
•video/mp4 : fichier vidéo codé en mp4
Les Promises sont des objets javascript qui sont associés à l'achèvement (ou l'échec) d'un traitement asynchrone et qui ont 3 états : pending (en attente), fullfilled (tenue ou résolue), rejected (rejetée), états qui ne sont généralement pas considérés par le programmeur. Ces objets permettent de gérer les callbacks appelées pour ce traitement de manière plus lisible et d'éviter leur enchevêtrement.
Les Promise sont généralement fournies par les API de communication pour les opérations réseaux, mais on peut en créer comme ceci :
const maPromesse = new Promise(executeur);
function executeur(onOk, onEchec) {..};
avec un constructeur qui prend en argument un executor (c'est à dire une callback de définition de la Promise). Cet executor prend lui même deux arguments :
•le premier onOK (nom conventionnel resolve) est la callback qui doit etre utilisée quand le traitement a réussi pour mettre à disposition l'objet résultat du traitement. Si Truc est la classe de l'objet résultat on dit que la Promise est une Promise de Truc et on écrit en pseudo TypeScript Promise<Truc>
•le deuxième onEchec (nom conventionnel reject), faculatif, est une callback qui est appelée en cas d'échec du traitement avec pour argument un objet décrivant cet échec (le texte "Erreur !" par exemple.)
Pour ajouter des arguments (arg1, arg2,...) à la promesse on est obligé de passer par l'intermédiaire d'une fonction qui renvoie la Promise, comme ci-après avec une fonction anonyme fléchée qui utilise les arguments fournis en paramètre dans le corps de l'exécuteur
const maPromesse = function((arg1, arg2){return new Promise((callback1, callback2) =>
{
// Construction de l'objet res0 de type Truc, avec utilisation de arg1, arg2..
if (/* si succès construction res0 */) {
callback1(res0); // On utilise callback1 (trad : resolve) pour envoyer l'objet
} else {
// On construit un objet rapport d'erreur objectEchec
callback2(objetEchec); // envoyé par la callback2 (trad : reject)
}
});}
Ne pas oublier le return qui renvoie la promesse attendue.
Généralement on utilise également une fonction féchée pour définir la fonction anonyme ce qui finalement conduit à la structure suivante (avec les nom traditionnels resolve et reject à la plcae de callback1 et callback2 :
const maPromesse = (arg1, arg2) => new Promise((resolve, reject) => {
//.. Construction res0 en fonction de arg1, arg2 et objetEchec si échec
let res0 = "Le résultat du travail", objEchec = "Erreur !";
if (/*reussite*/) resolve(res0);
else reject(objEchec);
});
Remarque : Les noms conventionnels des 2 callbacks : resolve, reject sont arbitraire, seul compte l'ordre. La première callback est celle qu'il faut appeler pour transmettre le résultat du travail, la seconde dont la présence en argument est facultative, est celle que l'on appelle en cas d'échec d'élaboration du résultat du travail.
Il y a aussi (accessoirement) les méthodes statiques suivantes :
•Promise.resolve(x) qui permet de créer un Promise<Truc> résolue (résolue c'est-à-dire associée à un traitement achevé et réussi) avec pour résultat un objet x de classe Truc.
•Promise.all([p1, p2, ...]) qui permet de créer un objet Promise unique enchainant les Promises p1, p2, ... qui s'exécuteront en parallèle avec synchronisation. La méthode then de cet objet étant exécutée seulement quand toutes sont résolues. Elle reçoit comme argument le tableau des résultats (dans l'ordre de p1, p2,..). La méthode catch étant exécutée dès la première erreur.
•Promise.allSetled([p1, p2, ...]) qui attend l'achèvement de toutes les Promises et renvoie pour résultat à la méthode then les états des promises p1, p2.
•Promise.race([p1, p2, ...]) qui renvoie le résultat de première promesse qui se termine (résolue ou rejettée).
L'objet Promise offfre deux principales méthodes then(onResult) et catch(onError) pour indiquer comment utiliser les résultats:
•then est utilisé pour enregistrer la callback onResult (nom arbitraire) qui est appelée, lorsque le traitement à réussi, afin d'utiliser le résultat de ce traitement.
•catch est utilisé pour enregistrer la callback onError ( nom arbitraire) qui sera appelée pour dire quoi faire en cas d'échec.
La callback onResult de la méthode then reçoit en argument un objet un objet result de type Truc provenant de la réussite du traitement. Cest pour cette raison qu'on l'appelle une Promise de Truc.
Souvent les callbacks onResult et onErr sont directement écrites sous forme de fonctions fléchées à l'emplacement de l'argument de then et catch.
promise.then((res)=>{-corps de onResult-}).catch((err)=>{-corps de onErr-});)
La callback onResult exploite son argument res et peut éventuellement retourner un nouveau résultat de classe quelconque Toto (qui sera automatiquement converti en Promise de Toto si ce n'en est pas déjà un, par la méthode statique Promise.resolve(resultat)), ce qui permet d'enchainer un deuxième then derrière le premier pour enregistrer une nouvelle callback et ainsi de suite.
Si aucun résultat n'est retourné, la méthode catch est chainée ensuite pour traiter les erreurs succeptibles de s'être produites à la création de maPromesse ou dans une des méthodes then.
Exemple de définition des callbacks qui exploiteront le resultat res0 en cas de réussite.
maPromesse(arg1, arg2)
.then((res0) => {
// Code exploitant res0 et générant res1
return res1
})
.then((res1) => {
// Code exploitant res1 et générant res2
return res2
})
.then((res2) => {
// Code exploitant res2. PAS de RETURN
})
.catch((err) => console.error(err)); // Si message d'erreur
Remarque :
- catch attrape aussi les erreurs positionnées dans un then, par exemple par throw new Error("Oops")
- Si la callback d'un then ne retourne rien, et qu'elle est suivi par un autre then, ce dernier recevra undefined.
Résumé :
promise.then((res)=>{-onResult-}).catch((err)=>{-onErr-});
•La méthode then(onResult) permet d'enregistrer la callback onResult(res) qui sera appelée lorsque le résultat res sera disponible en cas de réusite.
•La méthode catch(onErr) permet d'enregistrer la callback onErr(err) qui sera appelée en cas d'erreu ou d'échec.
Le mot clé async précédant une fonction fait que cette fonction va retourner un objet Promise. Soit c'est l'objet Promise qu'elle retournait déjà sans async, soit, une promise est automatiquement généré par l'interpréteur en transformant l'objet x retourné à l'aide d'un Promise.resolve(x). Exemple :
let coucou = async function hello() {
return "Hello";
}
Le "Hello" est transformé en promise par l'interpréteur. Comme cette fonction renvoie une promise, on peut lui associer des callback via des then et catch comme ci-après :
coucou()
.then((res)=>console.log(`Résultat = ${res}`))
.catch((err)=>console.log(`Erreur : ${err}`));
Le mot clé await ne peut être utilisé que :
1.devant un objet Promise,
2.à l'intérieur d'une fonction async ou bien au plus niveau d'un module.
Il met l'exécution en attente jusqu'à l'achèvement de la Promise ou son échec et renvoie son résultat res quand il est resolu ou génère une exception quand il y a rejet.
Ainsi le mot clé await permet de récupérer le résultat d'une Promise sans définir de callback onResult via un then(onresult). Comme une erreur est susceptible d'intervenir il est fortement recommandé de mettre cet await dans un try / catch pour traiter l'erreur.
try {
let res = await coucou();
console.log(`Résultat = ${res}`)
}
catch (err) {console.log(`Erreur : ${err}`)}
Ce code doit marcher dans le cas d'un <script type = "module">. Si ce n'est pas le cas (navigateurs anciens), il faut l'encapsuler dans une fonction async :
let bidon = async () => {
try {
let res = await coucou();
console.log(`Résultat = ${res}`)
}
catch (err) {console.log(`Erreur : ${err}`)}
}; bidon();
ou plus plus simplement et plus explicitement :
async function()
{
try {
let res = await coucou();
console.log(`Résultat = ${res}`)
}
catch (err) {console.log(`Erreur : ${err}`)}
}(); // Exécution au niveau de la définition !!!!
Blocage thread/fonction : :
await bloque la fonction async en attente du résultat, mais le reste du thread continue de s'exécuter, d'où l'interêt de l'appeler au niveau d'une fonction async et pas au niveau racine dans le cas d'un script de type module.
Si on met un await au niveau racine dans un script de type module, ce module va être bloqué. Il y a donc intérêt, juste après le await, à mettre la fin à ce module et à en ouvrir un autre de suite après.
Les données sont souvent stockées en mémoire usager dans des objets de type Blob (Binary Large Object, mais en fait il peut être très petit). C'est une structure qui permet de manipuler des pseudo-fichiers de n’importe quel type MIME. Cette structure n'a d'existence en mémoire que du coté navigateur (front-end) et pas coté serveur (back-end).
Un blob est un objet immuable (ne peut pas être modifié après sa création). Il est construit à l’aide du constructeur suivant :
const unBlob = new Blob ([donnees], options);
•donnees : Le premier argument est l’objet initial (un string, un objet javascript quelconque, une image.... qui doit obligatoirement être inclus dans un tableau entre [...].
•options : Un objet de format JSON donnant des renseignement sur les données, généralement limité à {type : ‘typeMime’}
Exemple :
const blob = new Blob(["Bonjour tout le monde !"], {type : "text/plain"});
Ici la donnée du blob est un texte ordinaire (un string), qui doit obligatoirement être inserré dans un tableau d’où les crochets autour, suivi de options qui est l'objet structuré au format JSON qui fournit le type MIME.
Les principaux membres des blobs sont :
•.size : la taille des données
•.type : son type MIME
•.slice([début[, fin[, contentType]]]) : méthode qui renvoie un blob qui est la tranche contenue entre début et fin.
•.close() : méthode pour fermer l’objet et libèrer éventuellement la mémoire occupée (peu utilisé)
•.isClosed : true s’il ne peut plus être lu (peu utilisé)
Ils offrent également deux méthodes spéciales qui renvoient un objet Promise de texte (string) ou d'ArrayBuffer binaire
•.text() : renvoie un objet Promise<String UTF-8>
•.arrayBuffer() : renvoie un objet Promise<ArrayBuffer>.
Les Blobs sont très utilisés comme parents des objets fichiers javascript File qui lui ajoutent des méthodes spécifiques comme FileReader pour lire les fichiers, mais aussi pour générer des URL Blob qui sont utilisées par les éléments du DOM pour afficher des textes ou des images externes.
Le constructeur new Blob permet de construire facilement un blob à partir d’un texte de provenance quelconque contenu dans des trings, par contre pour des images on doit passer par des chemins détournés, présentés par la suite.
Les attributs src des éléments <script>, <img>... prennent soit un simple nom de fichier, soit une URL web classique du style "http://xyz...", mais à l’intérieur du code javascript on assigne aux propriétés .src des éléments ('a', 'img',..) une URLblob qui est une URL associée à un blob
Les URLblob (de Uniform Resource Locator et blob déjà défini) sont des adresses uniformisées de données générées dynamiquement qui sont utilisées pour faire des transferts des données.
L'objet URLblob crée une URL qui référence directement les données du blob sans les dupliquer, au moyen de l’instruction :
const myUrl = URL.createObjectURL(blob);
Exemple :
<!DOCTYPE html>
<script>
const myBlob = new Blob([’Bonjour tout le monde !’], {type : ‘text/plain’}); console.log("myBlob = "+myBlob)
const myUrl = URL.createObjectURL(myBlob);
console.log("myUrl = "+myUrl)
</script>
et sur la console on obtient :
myBlob = [object Blob]
myUrl = blob :null/e3275f2c-4a3b-419d-a70d-ac6bd4be3ebd
Cet objet myUrl contient une adresse opaque qui permet d’accéder via l’URL aux données du blob. Elle n’est valide que dans le document actuel et tant qu’il est ouvert.
Ces objets ont tendance à envahir la mémoire disponible. Heureusement, il existe une instruction qui permet de la libérer lorsque le transfert est terminé :
URL.revokeObjectURL(myUrl);
Cette instruction supprime la référence à l’URLblob ce qui permet au gestionnaire de mémoire de récupérer celle allouée au blob (s’il n’y a pas d’autres références).
Remarquons que cette instruction ne doit être appelé qu’après la complétude de ce téléchargement. Si celle ci est effectuée dans une callback, comme dans le cas de l’instruction :
canvas.toBlob(callback, ‘typeMime’);
l’instruction revokeObjectURL ne doit pas la suivre immédiatement, car il y a des chances qu’elle détruise l’url avant l’achèvement de la callback, mais être simplement mise à la fin de cette callback.
Remarque :
L’objet statique URL (c’est-à-dire window.URL) propriétaire de la méthode .revokeObjectURL est ou était remplacée sur les anciens navigateurs Safari et Chrome par l’objet webkitURL (c’est-à-dire window.webkitURL), ce qui fait que l’on trouve parfois à la place de URL.propriétéXYZ la construction (webkitURL || URL).propriétéXYZ. Dans ce cas si webkitURL n’est pas null, c’est lui qui sera choisi, sinon c’est URL qui sera choisi pour appliquer la propriétéXYZ.
C'est un autre format utilisé pour d'incorporer directement des données dans une URL, dans lequel on inclut les ressources comme des images, des fichiers CSS ou JavaScript directement dans le code HTML ou CSS sans avoir besoin de fichiers externes.
Une Data URL se compose de plusieurs parties :
1.Préfixe data qui indique qu'il s'agit d'une Data URL.
2.Type MIME qui spécifie le type de contenu (par exemple, image/png, text/plain, application/pdf, etc.).
3.Encodage (optionnel) : Par exemple, base64 pour indiquer que les données sont encodées en base64.
4.Données : Les données elles-mêmes, encodées si nécessaire.
Exemple :
const duImg = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACN"+
"byblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
Utilisé pour incorporer des images directement dans le HTML ou le CSS, inclutre des fichiers CSS de style ou des scripts JS directement dans le HTML, incoprporer des fichiers binaires comme des PDFs ou des fichiers audio.
Les Data URL offrent l'avantage d'une réduction des requêtes HTTP : Moins de fichiers à charger, ce qui peut améliorer les performances et plus de simplicité car toutes les données sont contenues dans une seule URL.
Mais elles augmentent la taille des données et ne sont pas mises en cache par le navigateur, ce qui peut augmenter la consommation de bande passante si elles sont utilisées fréquemment.
On sauvegarde facilement une dataURL au moyen d'une ancre en définissant par ancre.download = nomFichier la destination par l'affectation ancre.href = duImg;.
Si c'est une image on l'affecte directement à une const img = new Image(); à l'aide de sa méthode img.src = duImg;
En donnant à l'attribut .download d'une ancre un nom de sauvegarde, le contenu d'un blob peut-être enregistré dans le dossier Téléchargement par défaut du Navigateur, lors du clic de l'utilisateur sur le lien.
Exemple :
<!DOCTYPE html>
<script>
let ancre = document.createElement('a');
ancre.download = ‘cava.txt’ ; // Nom donné au fichier d'enregistrement
let blob3 = new Blob([’Comment ça va Michel ?’], {type : ‘text/plain’});
ancre.href = URL.createObjectURL(blob3);
ancre.click();
URL.revokeObjectURL(ancre .href);
</script>
Dans cet exemple on crée un élément <a> (balise dite anchor, c’est-à-dire ancre) assignée à la variable ancre sans l'affilier au document (orpheline), on affecte à l'attribut .download le nom choisi pour le fichier et on envoie .click() par programme ce qui permet de faire la sauvegarde sans intervention de l'usager !!!
Le fait d’instancier l'attribut .download configure la balise <a> en balise de déchargement, alors que si on ne l’instancie pas c’est le document qui va être remplacé par le contenu du blob, comme quand on change de page.
C'est sécurisé dans certains Navigateur où la destination du fichier pourra être modifié par l’utilisateur avant l’enregistrement effectif, mais j'ai l'impression que dans Chrome ce n'est pas le cas.
Si dans une balise <img> on a pour attribut src="fichier.txt", celui-ci sera recherché sur le serveur distant dans le même répertoire que le fichier index.html. Si on exécute un fichier index.html sur notre ordinateur local, le navigateur ira chercher fichier.txt sur notre ordinateur local dans le répertoire du fichier index.html, et cela fonctionnera. Cela fonctionnera aussi pour tout fichier situé sur l’ordinateur distant ou local situé à une adresse valide, et autorisée.
Exemples :
<img id="idimg" src="http://nostarch.com/images/car.png">
<img id="idimg" src="cristal.jpg">
<img id="idimg" src="c:/tmp/navires.jpg">
Dans le 1er cas, si l’image existe à l’adresse indiquée elle sera affichée.
Dans le 2ème cas, l’adresse de l’image est relative à l’adresse de la page (page.html par exemple). Que la page soit exécutée par un chargement depuis un serveur distant, ou activée sur l’ordinateur local, le chargement de l’image se fera effectivement si le fichier se trouve dans le même répertoire que page.html.
Dans le 3ème cas si page.html provient d’un serveur, le navigateur va essayer de charger le fichier image depuis l’ordinateur du serveur, et il y a des chances que ce fichier n’existe pas, car les adresses des fichiers des serveurs sont rarement absolues. Si la page.html est exécuté depuis l’ordinateur client lui-même, le chargement devrait avoir lieu si l’image existe bien à cette adresse sur Chrome, mais ça ne marche pas sur Firefox qui est plus strict : Sur Firefox l’attribut src n’accepte pas les adresses absolues pour protéger les utilisateurs. Il faut passer par l’intevention de l’utilisateur qui sélectionne lui-même l’adresse du fichier
Les fichiers sont représentés en javascript par la classe File qui héritent de la classe Blob. Les objets fich de cette classe fournissent, en plus de celle du Blob, les propriétés suivantes :
•fich.lastModifiedDate : string date TU de dernière modification. Ex : Thu Mar 19 2020 14 :34 :16 GMT+0100 (heure normale d’Europe centrale)
•fich.lastModified : date TU de dernière modification en millisecondes écoulées depuis le 1/1/1970 0h TU. Ex pour la date ci-dessus : 1584624856199 (qui correspond à 13h34m16s et 199 ms. TU.)
•fich.name : Le nom du fichier (sans le path)
et aussi des attributs du blob, éventuellemnt adaptés : fich.size : (surcharge du size du Blob) la taille du fichier en octets fich.type : string type MIME du fichier.
L’accès aux fichiers est compliqué dans javascript car l’ordinateur local doit être protégé des tentatives d’intrusions externes. Ainsi, il y a pas d’instruction pour créer un répertoire, par contre lorsque l’utilisateur fait une sauvegarde comme dans l’exemple précédent, l’ouverture de l’exploration de fichier lui permet de créer des répertoires, déplacer des fichiers... En utilisation normale la page qui s’exécute sur notre ordinateur local provient d’un fichier externe, par exemple index.html qui est située sur un serveur distant..
L’objet FileReader permet de lire les données de nombreux types de fichiers par le biais de différentes fonctions de lecture comme :
•readAsText(fichier[, codage]) pour lire les fichiers de texte avec codage = "utf-8" par défaut,
•readAsArrayBuffer(fichier) pour lire les fichiers binaires
•readAsDataURL(fichier) pour affecter ces données (codées en Base64) à une propriété src comme pour les éléments img.
Pour obtenir le résultat de la lecture il faut passer par ses propriétés événementielles et en particulier
- onload : on lui assigne une callback qui sera appelée lorque la lecture est terminée sans erreur. La propriété result contient le texte lu.
Les autres propriétés événementielles onloadstart (chargement commencé), onprogress (en cours), onabort (avorté), onloadend (terminé bien ou mal ?), onerror (erreur survenue, voir propriété error) sont moins intéressantes.
L’accès aux fichiers clients locaux en lecture se fait le biais d’un élément input du DOM. Cet élément ouvre un explorateur de fichiers pour effectuer cette sélection du fichier à lire.
On peut créer l’élément input en javascript , mais le plus simple c’est de le créer par une ligne HTML dans le <body> comme la suivante :
<input type="file" id="mybtn">
Son exécution produit l’ajout au document d’un bouton incorporant le texte Choisir un fichier, et suivi du texte Aucun fichier choisi tant qu’aucun choix n’a été fait, texte qui sera remplacé par le nom du fichier après le choix.
On peut spécifier un filtre des fichiers présentés en ajoutant l’attribut accept, par exemple :
accept=".txt, .js, .html, .php"
Tout ce comportement est géré automatiquement, mais il faut encore indiquer l’action à entreprendre lorsque l’utilisateur clique sur le bouton ce que l’on fait en affectant une callback à la propriété onchange du bouton. On peut mettre son nom, par exemple recup dans la ligne html précédente
<input type="file" multiple onchange="recup" accept=".txt, .js, .html, .php">
ou dans le source javascript, comme ceci.
Var selecteur = document.getElementById(’mybtn’);
selecteur.onchange = recup ;
Ensuite on écrit la fonction recup à exécuter lorsqu’un fichier est sélectionné par l’usager.
Dans la callback recup(), l’objet du DOM input sera représenté par la variable this. Cette variable offre la propriété files qui est le tableau des objets File sélectionnés. Quand la propriété multiple n’est pas présente le tableau ne comporte qu’un élément () qui est accédé par files[0].
Considérons l’objet :
var fich = this.files[0];
qui est le premier (ou unique) des fichiers sélectionnées.
Dans certains navigateurs, on peut utiliser fich directement comme ceci :
document.getElementById('idimg').src = fich.name ;
mais cela ne marche pas toujours et généralement il faut passer par un objet URL généré à parir de fich comme ceci :
document.getElementById(’idimg’).src = (webkitURL||URL).createObjectURL(fich);
Après le chargement de l’image l’objet peut être libéré. Pour cela on pourra utilisera l’événement onload de l’image qui appelle sa callback à l’issue du chargement.
Document.getElementById(’idimg’).onload = function() {(webkitURL||URL) .revokeObjectURL(this.src); }
Exemple : affichage d’un fichier texte local :
partie html :
<input type="file" id="idintext"/>
<textarea placeholder="Le texte sera affiché ici."></textarea> où placeholder permet de préciser un texte en grisé qui n’est présent que quand il n’y a aucun texte normal.
Partie script :
document.getElementById(’idintext’).onchange = recupTxt ; //avec la callback suivante :
function recupTxt() {
if (this.files.length == 0) return null ;
let reader = new FileReader();
reader.readAsText(this.files[0]);
reader.onload = function()
{ document.querySelector(’textarea’).value = reader.result ;}
}
La callback de onchange pourrait être assignée directement dans la balise input du html comme ceci :
<input type="file" id="idintext" onchange="recupTxte(event)"/>
mais dans ce cas il faut préciser l’objet événement global window.event en argument de la callback car la variable this ne correspond plus à rien car l’objet javascript équivalent à document.getElementById(’idintext’) n’a pas été créé.
La callback prendrait alors la forme suivante :
function recupTxte(e) {
let files = e.target.files ; // ici c’est e.target qui représente l’élément input if (files.length == 0) return null ;
let reader = new FileReader();
reader.readAsText(files[0]);
reader.onload = function() { document.querySelector(’textarea’).value = reader.result ;}
}
La première méthode est préférable car l’événement global window.event sera peut-être déclaré deprecated dans un certain futur cf : https://www.beaubus.com/blog/what_is_js_windowevent_why_it_is_deprecated_and_what_to_use_instead.html
Les objets File qui héritent des objects Blob possèdent leurs deux fonctions de chargement asynchrones :
text() : renvoie une promise de texte UTF-8 dans un string
arrayBuffer() : renvoie une promise de données binaires dans un ArrayBuffer
Exemple :
<!DOCTYPE html>
<html> <body>
<input type="file" onchange="affiche(this.files[0])"> <pre id="output"></pre>
<script>
function affiche(file) {file.text().then(utilisation); } function utilisation(data)
{
document.getElementById(’output’).textContent = data ;
}
</script> </body></html>
Dans la callback affiche(file) affectée à onchange, on affecte la callback utilisation(data) par le biais de la méthode then de l’objet promise file.text(). La méthode positionne le texte reçu dans l’élément <pre> qui affiche en verbatim en respectant les sauts de ligne.
La manière dont les données data transitent est assez troublante et certains programmeurs qui ne s’y font pas préfèrent un mode de programmation bloquant mais qui fait apparaître explicitement la réception des données. Ce mode utilise le mot clé away qui étant placé avant l’objet promise permet de récuperér ces données.
Voici un exemple de lecture d’un fichier texte local :
<!DOCTYPE html>
<html> <body>
<input type="file" onchange="affiche(this.files[0])"> <pre id="output"></pre>
<script>
async function affiche(file) {
let data = await file.text();
document.getElementById(’output’).textContent = data ;
}
</script> </body></html>
Le mot clé await ne peut être utilisé que dans une fonction déclarée async car elle produit un blocage du déroulement du programme jusqu’à la fin du chargement.
C’est la méthode la plus simple pour accéder à un fichiet texte.
Par ailleurs dans le cas d’un accès multiple, lire plusieurs fichiers à l’aide d’un reader.readAsText(files[i]);
reader.onload = function() { elem.value = reader.result ;} dans la boucle ne va marcher que pour le dernier élément, car il semble que chaque nouveau reader détruit le précédent. Alors qu’en bloquant le processus par un await, on arrive à récuperer toutes les données.
Dans le sélecteur input peut choisir les extensions avec l’attribut accept.
<input type="file" id="idinimg" accept=".jpg, .png"/> On affichera l’image dans une zone <img> définie comme suit dans le html.
<img id="idimg" width="400" height="400">
On peut fixer la callback à exécuter lors de la sélection dans la balise ou dans le javascrip, comme ceci :
var inImg = document.getElementById(’idinimg’).onchange = recupImg ; function recupImg() {
if (this.files.length == 0) return null ; let reader = new FileReader();
reader.readAsDataURL(this.files[0]);
let myImg = document.getElementById(’idimg’); reader.onload = function(){myImg.src = this.result ;}
}
Dans ce cas l’image est lue dans l’objet File par un objet fileReader. La lecture peut durer un certain temps, c’est pour cela qu’on doit fournir une callback à la methode onload de l’objet reader. Cette callback sera appelée à la fin du chargement et elle disposera de la donnée lue dans this.result (c’est-à-dire reader.result).
On peut également charger l’image dans l’objet img en affectant à sa propriété src une URL Blob du fichier. La callback s’écrit alors simplement comme suit :
function recupImg() {
if (this.files.length == 0) return null ; let myImg = document.getElementById(’idimg’); my_img.src = (webkitURL || URL).createObjectURL(this.files[0]);
}
Après le chargement de l’image l’objet peut être libéré. Pour cela on peut utilisera l’événement onload de l’image :
my_img.onload = function() {(webkitURL||URL).revokeObjectURL(this.src);} Utilisation d’une image chargée par le navigateur Une image chargée par le navigateur dans une balise peut être utilisée dans un autre but. On peut par exemple en extraire une partie pour la plaquer dans un canevas (qui sera présenté plus loin). Exemple : <!DOCTYPE html>
<html><body>
<img id=’idImg’ src="treasuremap.png">
<canvas id="c1" width=400, height="400" ></canvas> <script>
let elemimg = document.getElementById(’idImg’); let ctx = document.getElementById(’c1’).getContext(’2d’); elemimg.addEventListener(’load’, function(){ctx.drawImage(elemimg, 50,50, 100, 100,0,0,100,100);}); </script></body></html>
La variable elemimg qui désigne la balise <img> peut être utilisé comme argument de type Image de la méthode de plaquage d’image drawImage dans le contexte d’un canavas (présenté au chapitre Pour dessiner : Le canevas). Pour faire ce plaquage il faut attendre que l’image soit effectivement chargée. C’est pour cela qu’il est effectué dans une callback qui est appelée à la fin de son chargement. Pour cela on a associé à l’événement load de l’élément image elemImg la callback de plaquage. On peut positionner cette association avec l’attribut onload comme ceci :
<!DOCTYPE html>
<html><body>
<img id=’idImg’ src="treasuremap.png" onload="placage()"> <canvas id="c1" width=400, height="400" ></canvas> <script>
let elemimg = document.getElementById(’idImg’); let ctx = document.getElementById(’c1’).getContext(’2d’); function placage() { ctx.drawImage(elemimg, 50,50, 100, 100,0,0,100,100);} </script></body></html>
Pour accéder aux fichiers distants situés sur un autre serveur (ou éventuellement sur notre ordinateur si la page chargée par le navigateur réside sur lui), on utilise des requêtes qui fonctionnent en mode asynchrone, afin d’éviter de bloquer l’ordinateur en attente de la réponse. Cette méthode est connue sous le nom d’AJAX, pour Asynchronous Javascript and XML, mais ce terme est de moins en moins usité.
Cette programmation assez complexe n’a d’intérêt que pour réaliser des pages webs destinées à être publiées.
ATTENTION. Pour faire ces accès distants il faut que le fichier soit exécuté sur un ordinateur en mode serveur. Il faut donc installer un serveur sur l’ordinateur (Wamp par exemple) et appeler la page html par le biais du localhost du réseau interne simulé dans l’ordinateur, sinon on aura l’erreur suivante :
Access to XMLHttpRequest at ‘file:///.... from origin ‘null’ has been blocked by CORS policy : Cross origin requests are only supported for protocol schemes : http, data, isolated-app, chrome-extension, chrome, https, chrome-untrusted.
Nous donnons ci-après l'ancienne manière de programmer les communications asynchrones à l'aide des requêtes XMLHttpRequest. C'était assez lourd et fastidieux. On peut sauter cette section et passer directement à la méthode actuelle basée sur fetch/promise, section 22.6.2.
La communication entre le programme javascript et le serveur se fait via des requêtes qui font appel à une fonction nommée XMLHttpRequest (XHR en abrégé) qui permet d’obtenir les données au format XML à l’origine puis HTML, JSON et même en simple texte à l’aide d’une requêtes HTTP, c’est-à-dire qui suit le protocole de communication client-serveur hypertexte développé pour le World Wide Web.
Utiliser des requêtes dans ce cas c’est utiliser un marteau pilon pour écraser une mouche mais cela marche.
Pour récupérer des données du serveur, par exemple le contenu d’un fichier texte fich.txt, on envoie une requête XMLHttpRequest de type GET (il y a aussi le type POST un peu plus compliqué) avec l’url du fichier à récupérer
Pour récupérer un fichier texte on peut procèder de la manière suivante :
<!DOCTYPE html>
<html><head></head><body>
<pre id="idPre"></pre>
<script>
var url = "_contenu.txt" ; // l’URL du fichier cherché sur le serveur
var myrq = new XMLHttpRequest(); // Création requête vide
myrq.open("GET", url); // En mode GET, sync omis => asynchrone par défaut
myrq.responseType = ‘text’ ; // inutile ici, car text par défaut
myrq.onload = function ()
{ // callback a exécuter à la réception
document.getElementById("idPre").textContent = this.responseText ;
// ou bien = myrq.response;
// ou bien = myrq.responseText;
}
myrq.send(null); // Envoi de la requête
</script></body></html>
Dans la callback les données sont accessibles par le biais des attributs this.response (cas général) ou this.responseText (cas texte). En général on affecte ces données à un élément de la page par le biais de innerHTML ou textContent si c’est du texte simple non formaté.
La propriété responseType (peu utilisée) spécifie le type de donnée attendu. Il peut être un des types suivants :
•"" : non spécifié, idem "text"
•"text" : du texte.
•"arraybuffer" : un javaScript ArrayBuffer contenant des données binaires.
•"blob" : données a priori binaires
•"document" : doc HTML, XML ou XMLD
•"json" : un object javascript de style json
Les données attendues, quel que soit le responseType seront disponible dans request.response, mais on peut-être plus précis et si on attend :
•du texte, on précisera le responseType "text" et on le récupèrera sous forme de string par request.responseText
•du XML ou HTML, on précisera le responseType "document" et on le récupèrera avec request.responseXML qui conservera la structure xml, permettant une découpe avec un parser approprié
•une image, on précisera le responseType "blob" et pour récuperer les données on pourra passer par le biais de l’URL blob associée, comme suit :
let image = document.createElement(’img’);
image.src = URL.createObjectURL(request.response); document.body.appendChild(image);
En résumé
•L’objet XMLHttpRequest est d’abord créé par un new.
•L’appel de la méthode open("mode", ‘url’, async) permet de préciser :
◦le "mode" : "GET" ou "POST" (mais aussi "HEAD", "PUT" et "TRACE")
◦l’’url’ de la cible recherchée
◦le traitement est effectué en mode asynchrone async (true par défaut si absent) ou synchrone (false) de la requête.
•En mode asynchrone la propriété onload = reqListener permet de préciser la callback à exécuter lorsque la requête sera satisfaite.
•L’appel de la méthode send(data) envoie la requête au serveur (data = null ou absent en mode "GET" et contient les données en mode "POST"). En mode synchrone, elle bloque l’exécution jusqu’à la complétude du transfert.
Le fonctionnement préconisé est le mode asynchrone non bloquant, présenté en exemple au début avec l’attribut async=true ou absent dans open("mode", ‘url’, async). Ce mode n’est pas bloquant et on traite la réponse dans la callback reqListener (affectée à l’attibut onload) qui sera appelée à la fin du transfert.
Si on envoie la requête en synchrone (async=false), l'exécution reste bloquée après le send(data) jusqu'à l'accomplissement de la requête. Il faut alors tester la propriété status (ou/et statustext) qui contient le status HTTP renvoyé par le serveur pour savoir si la requête est satisfaite ou non.
Dans le cas où on utilise la méthode "POST", par exemple pour envoyer le message data au moyen de send(data) il faut, au préalable envoyer des compléments au moyen de la méthode setRequestHeader, comme ceci :
request.setRequestHeader("Content-type","application/x-www-form-urlencoded") request.setRequestHeader("Content-length", data.length) request.setRequestHeader("Connection", "close")
On peut surveiller son déroulement pendant l’attente de la réponse à la requête (facultatif en asynchrone) à l’aide d’une callback précisée au moyen de la propriété onreadystatechange de la requête :
request.onreadystatechange = checkEtatRequete ; Dans cette fonction on testera la valeur des propriétés readyState et status pour connaître l’avancement de la requête. La propriété readyState peut prendre 5 valeurs : 0 : non initialisée
•1 : chargement en cours
•2 : chargée
•3 : interactive
•4 : terminée
Le status de la réponse HTTP peut prendre les valeurs suivantes (fréquents en gras) : 100 à 199 : informations (?)
•200 à 299 : réussites
◦200 : OK
•300 à 399 : redirections
•400 à 499 : erreurs client
◦400 : mauvaise requête
◦401 et 407 : non autorisé
◦403 : interdit
◦404 : non trouvé
◦410 : Gone
•500 à 599 : erreurs serveur
On envoie la requête en mode synchronisé en mettant async=false dans open("mode", ‘url’, async). L’exécution reste bloquée après le send(data) jusqu’à l’accomplissement de la requête. Il faut alors tester la propriété status (ou/et statustext) qui contient le status HTTP renvoyé par le serveur pour savoir si la requête est satisfaite ou non.
Voici un exemple (DECONSEILLÉ) de lecture et affichage d’un fichier texte en mode synchrone. Le mode synchrone est DECONSEILLÉ car il peut entrainer un blocage. Ce même exemple peut être transformé en asynchrone en mettant true à la place de false et il marchera aussi bien.
<!DOCTYPE html><html><head></head><body><script>
var url = "liste.txt";
var myrq = new XMLHttpRequest();
myrq.open("GET", url, false);
myrq.onreadystatechange = function ()
{
if(this.readyState == 4) { // Si la requête est servie
if(this.status == 200 || this.status == 0) // et si le status est ok
{
if(this.responseText != null) alert(this.responseText);
else alert("Erreur de communication : Aucune donnée reçue")
} else alert( "Erreur de communication : " + this.statusText)
}
}
myrq.send(null);
</script></body></html>
Remarques :
•La callback est affectée à la propiété onreadystatechange de l’objet myrq, ainsi dans le corps de cette callback this fait référence à cet objet.
•Dans la callback précisée à l’attribut onload de la requête, les données sont accessibles par le biais des attributs this.response (cas général) ou this.responseText (cas texte). On peut afficher ces données en les affectant à un élément de la page (par le biais de innerHTML par exemple).
Dans le cas de données distantes accédées via une url de type http://.., la lecture sur le serveur distant ne peut pas être effectué en javascript qui ne dispose pas de fonction permettant cette lecture. Il faut passer par l’intermédiaire d’un petit programme (en php par exemple) situé sur le serveur de la page en cours qui va effectuer le chargement du fichier distant et qui fournira ensuite à la donnée chargée.
La communication fait plusieurs trajets :
•La partie javascript exécutée dans le navigateur (le client) envoie la requête à une application php située sur le serveur.
•Cette application récupère les données à l’adresse indiquée (serveur lui-même ou ailleurs) et génère avec un document qu’il envoie au navigateur
•Le navigateur recupère la donnée dans la réponse à sa requête.
Comme on le voit c’est assez compliqué, et ce n’est vraiment pas la bonne méthode pour accéder aux données situées sur le même ordinateur.
Considérons la page web suivante :
<!DOCTYPE html><html><head><meta><meta charset="utf-8"></head><body> <div>Avant-[<scan id=’info’></scan>]-après.</div> <script>
request = new XMLHttpRequest();
var trompeCache = "&bidon="+Math.random()*1000000; request.open("get", "urlget.php?my_url=https://www.llibre.fr/hello.txt"+trompeCache);
request.onload = function()
{ document.getElementById(’info’).textContent = this.responseText ;} request.send(null)
</script>
</body>
</html>
La cible à laquelle on fait le "get" contient l’adresse suivie urlget.php de ? qui indique des paramètres à transmettre à l’adresse en question qui seront dans un tableau de paramètres nommé $_GET[]. On ne transmet qu’un paramètre nommé my_url à qui on donne la valeur :
"https://www.llibre.fr/hello.txt"+trompeCache Ignorons +trompeCache pour le moment. On fait donc une requête GET à la page urlget.php avec le paramètre $_GET[’my_url’]= "https://www.llibre.fr/hello.txt"+trompeCache Le programme (urlget.php) résidant sur le serveur qui reçoit la requête est le suivant :
<?php if (isset($_GET[’my_url’])) echo file_get_contents($_GET[’my_url’]);
?>
Il vérifie la présence de l’argument attendu avec la méthode isset.
Il possède une fonction très puissante file_get_contents(adresse) qui lui permet de charger (à travers le réseau ou sur place) le contenu situé à l’adresse qui lui est fournie en argument, à savoir :
file_get_contents("https://www.llibre.fr/hello.txt"+trompeCache) Le serveur demande ce fichier en accompagnant la demande d’un paramètre bidon=nb_aléatoire différent à chaque fois, ce qui est une astuce pour faire systématiquement la demande plutôt que de prendre une réponse précédente présente dans le cache, mais avec un paramètre bidon différent du paramètre spécifié.
Ensuite la donnée reçue est, au moyen de la fonction echo écrite sur une page qui est envoyée au navigateur. Lorsque le naviagetur reçoit cette page il active la callback passée à onload qui dans notre cas l’affiche dans le scan info <div>Avant [<scan id=’info’></scan>] après.</div>.
Le résulat est le suivant :
Avant-[Bonjour visiteur !]-après.
Remarque sur la sécurité : Pour éviter de récupérer du code malveillant reçu dans la partie php, on le filtre comme suit : file_get_contents(stripslashes(htmlentities(strip_tags($_GET[’my_url’]))));
Remarque sur la surveillance : A la place de la callback affectée à onload, on pourrait utiliser, comme dans l’exemple de transfert synchrone, l’attribut readyState dans une callback affectée à la propriété onreadystatechange, pour surveiller l’avancement du transfert et appeler la callback de traitement quand request.status == 200 (transfert ok) au lieu de passer par onload.
C’est nettement plus compliqué mais ça marche : <!DOCTYPE html><html><head><meta><meta charset="utf-8"></head><body> <div>Avant[<scan id=’info’></scan>]après.</div> <script>
request = new XMLHttpRequest();
var trompeCache = "&bidon="+Math.random()*1000000; request.open("get", "urlget.php?my_url=https://www.llibre.fr/hello.txt"+trompeCache);
request.onreadystatechange = function()
{
if (this.readyState == 4) {
if (this.status == 200) {
if (this.responseText != null)
document.getElementById(’info’).textContent = this.responseText else alert("Erreur de communcation : Aucune donnée reçue") } else alert( "Erreur de communcation : " + this.statusText)
}
}
request.send(null)
</script>
</body>
</html>
L’attribut onload n’est pas spécifié.
L’utilisation d’une requête de type "post" est un peu plus lourde que celle de type "get".
<!DOCTYPE html><html><head></head><body>
<div>Before[<scan id=’info’></scan>]after</div> <script>
params = "mon_url=https://www.llibre.fr/hello.txt"; request = new XMLHttpRequest()
request.open("POST", "urlpost.php")
request.setRequestHeader("Content-type","application/x-www-form-urlencoded") request.setRequestHeader("Content-length", params.length) request.setRequestHeader("Connection", "close") request.onload = function()
{ document.getElementById(’info’).innerHTML =this.response ; } request.send(params);
</script></body></html>
Les lignes mises en grisé ne sont plus nécessaires et sont même interdites pour raison de sécurité.
Dans la requête "get", l’adresse à télécharger était passé dans un paramètre qui suivait l’url de la page php, après le point d’interrogation : my_url=https://www.llibre.fr/hello.txt
et la méthode send envoyait null : request.send(null) Avec la requête "Post", les paramètres envoyés à la page php sont fournis dans le string params argument de la méthode send : send(params); avec : params = "mon_url=https://www.llibre.fr/hello.txt"; Le programme PHP les récupère dans le tableau $_POST[..].
<?php
if (isset($_POST[’mon_url’]))
{ echo file_get_contents($_POST[’mon_url’]); }
?>
Comme l’url passée à la méthode open voyage en clair sur le réseau, les paramètres de la méthode get qui lui sont collés peuvent être piratés, alors que dans le cas de la méthode post ils sont envoyés séparément par la méthode send qui les crypte dans le cas des communications vers les sites https. C’est pour cela que la méthode post est éventuellement préférée.
Mais la requête post doit spécifier trois attributs supplémentaires dans une entête :
Content-type , Content-length et Connection, dont je ne conais pas très précisément les valeurs adéquates à leur affecter.
MODIF : 1 seul paramètre en plus : Content-type
Remarque : Comme pour l’exemple précédent avec get, on peut utiliser l’attribut readyState dans une callback affectée à la propriété onreadystatechange pour surveiller l’avancement du transfert et appelerla callback de traitement quand request.status == 200 (transfert ok) au lieu de passer par onload.
Cest la nouvelle méthode qui permet un codage concis pour enchainer des traitements des données.
La nouvelle manière de faire des requêtes utilise une méthode fetch(url) qui envoie la requête au serveur pour récupérer (ou non) des données qui correspondent à l’url qui peut comporter des arguments (couples nom=valeur) après un ? et séparés par des &.
Exemple : url = "https://www.llibre.fr?toto=3&titi=coucou".
Par ailleurs, lorsque l'url s'adresse au serveur de la page en cours, il est inutile de le préciser et le fetch peut être fait sous la forme fetch("?toto=3&titi=coucou"")...
Le serveur reçoit cette requête et renvoie le fichier si l'url précise un fichier et/ou traite les arguments qu'il reçoit par le biais de $_GET["toto"] et $_GET["titi"].
L'objet fetch(url) est un objet Promise qui offre ses méthodes then, catch... étendu de nouvelles propriétés pour l'objet Promise résolu (généralement nommé resolve) dont les propriétés :
•ok : true pour les status HTTP 200 à 299
•status : valeur du code HTTP (ex 200, 404)
•statusText : Le message associé (ex: "OK", "Not Found").
Cette fonction est suivie de méthodes .then(callA).then(callB).catch(callE) qui s'exécutent à la suite du succès de la précédente ou bien c'est la méthote catch(callE) qui s'éxécute dès qu'il y a une erreur.
Remarque :
fetch(url).then(callA).then(callB).catch(callE)
est équivalent à :
let a = fetch(url);
let b = a.then(callA);
let c = b.then(callB)
c.catch(callE);
La structure de cette méthode renvoyant des objets sur lequels on peut enchainer des méthodes then() et callback) est un interface nommée Promise et les objets renvoyés (a, b, c) sont appelés des promises. Dans le chapitre sur les objets Blob, nous avons rencontré leurs méthodes text() et arrayBuffer() qui offrent également cette interface.
Les arguments des méthodes then et catch sont :
•then (callback_reussite) si on ne veut pas préciser de traitement en cas d’échec ou
•then (null, callback_echec) si on ne veut pas préciser de traitement en cas de réussite. Ce cas a pour synonyme catch (callback_echec)
On trouve plus rarement la méthode then (callback_reussite, callback_echec)
Si l’objet Promise ne servait qu’à cela (permettre de préciser le nom des callbacks à appeler pour traiter les données en cas de réussite du transfert et éventuellement celle à exécuter en cas d’échec), ce serait un marteau pilon pour écraser une mouche. Il trouve son intérêt (pour certains) par le mécanisme de chainage des traitements qu’il permet.
La callback_reussite(response) prend ou reçoit les données dans son argument. Dans le cas d’une Promise générée par fetch elles sont d’un type spécial appelé Response. On code la callback en fonction du type de réponse attendue. Si le traitement est terminé, elle peut ne rien retourner, sinon il est préconisé qu’elle retourne l’objet de type Promise lui-même pour continuer le traitement sur cet objet, et ainsi de suite. C’est dans cette possibilité de chainer les traitements en cascade que certains trouvent un intérêt à cette méthode.
Dans cette succession éventuelle de chainage des traitements, on exécute la méthode then de la 1ere promesse renvoyée, et éventuellement des suivantes. Une seule méthode catch peut codée pour la dernière promesse codée. En cas d’erreur lors de l’exécution d’une quelconque des promesses, elle sera excécutée.
La callback_echec(strErr) prend et reçoit un argument de type string généré précisant le problème rencontré. On la code éventuellement pour faire du debug.
Exemple d’utilisation d’un objet Promise : Voici un exemple pris en dehors du contexte des communications, de l’utilisation d’un objet Promise. On doit faire exécuter une opération dont on ne connaît pas l’issue à l’avance. Voici les callbacks à exécuter suivant que le travail se passe bien ou mal :
function successCallback(résultat) {
console.log("L’opération a réussi avec le message : " + résultat);}
function failureCallback(erreur) {
console.error("L’opération a échoué avec le message : " + erreur);}
Cette opération est réalisée par la fonction requete suivante qui prend en argument les callbacks à appeler en cas d’échec et de réussite ; function requete(successCallback, failureCallback) {
// Ici le traitement de la requête qui prend du temps. Quand c’est fini : console.log("C’est fait");
// On tire au hasard pour voir si le traitement a réussi et suivant le cas // on appelle la callback réussite ou échec.
If (Math.random() > .5) { successCallback("Réussite");} else {failureCallback("Échec");}
}
On peut appeler la requête directement comme suit :
requete(successCallback, failureCallback);
ou bien passer par l’intermédiaire d’un objet promise comme suit :
pro = new Promise(requete)
pro.then(successCallback, failureCallback);
ce qui dans ce cas n’apporte pas grand-chose mais montre le mécanisme d’un objet Promise. Ce serait intéressant s’il y avait plusieurs traitements à chainer :
pro.then(okback1).then(okback2).then(okback3).catch(failureCallback);
Dans le cas de la méthode fetch, la donnée en argument de la callback réussite est de type Response. C’est une interface qui offre les propriétés principales suivantes :
•Response.headers : les objets Headers associés à la réponse.
•Response.ok : true si succès (statut HTTP entre 200 et 299) ou false.
•Response.redirected : indique (t/f) si la réponse est le résultat d’une redirection (la liste d’URLs a plus d’une entrée)
•Response.status : code de la réponse (par exemple 200 en cas de réussite).
•Response.statusText : message correspondant (à savoir : OK pour le code 200 par exemple).
•Response.type : (basic, cors ???).
•Response.url : l’URL de la réponse.
•Response.useFinalURL : booléen statuant s’il s’agit de l’URL finale de la réponse.
Et les méthodes suivantes :
•Response.clone() : Crée un clone de l’objet Response.
•Response.error() : nouvel objet Response associé à une erreur réseau.
•Response.redirect() : nouvelle réponse avec une URL différente permettant de rediriger l’utilisateur.
Response implémente (hérite de) Body, voici les différentes propriétés et méthodes qui sont disponibles :
•Body.body : un ReadableStream permettant de lire le contenu du body.
•Body.bodyUsed : booleén qui indique si le body a déjà été utilisé dans la réponse ou non.
•Body.arrayBuffer() : Renvoie une promise qui retourne un ArrayBuffer lorsqu’elle est résolue.
•Body.blob() : Renvoie une promise qui retourne un Blob lorsqu’elle est résolue.
•Body.formData() : Renvoie une promise qui retourne un FormData lorsqu’elle est résolue.
•Body.json() : Renvoie une promise qui retourne le résultat du parsing du body text, comme JSON ,lorsqu’elle est résolue.
•Body.text() : Renvoie une promise string
Ces dernières méthodes (arrayBuffer, blob, json, text,...) sont à exécuter sur l’objet Reponse pour transformer les données brutes en données utilisables.
Le Response.headers permet d’accéder aux informations sur la réponse via Response.headers.get(name) ou name sera par exemple "content-type" si on désire connaître le type des données.
Pour ramener des données du serveur, on crée un objet fetch :
var ff = fetch(url);
Puis on utilise sa méthode then qui prend en argument la callback qui sera appelée lorsque la donnée de type Response sera disponible. Cette méthode est invoquée tout de suite :
ff.then(callback1);
La callback va recevoir en argument la Response que nous nommons par exemple data. Elle va transformer cette Response en un objet proche du format attendu. On la programmera donc comme suit (en supposant que c’est du texte) :
function callback1(data) {
var pro = data.text(); // ici l’objet Response à une methode text() adéquate pro.then(callback2);
}
Dans cet exemple, lorsque l’objet pro (qui est un objet Promesse proche du format attendu) sera disponible, on appelle un autre fonction, callback2 pour le traiter (afficher le résultat en l’occurence) au moyen, ici aussi, de la méthode then de la l’objet Promesse. Cette callback va recevoir en argument les données classiques prêtes a être affichées. Elle sera donc de la forme :
function callback2(txt) { myDiv.textContent = txt ; }
Accès à un fichier texte :
<!DOCTYPE html><html><head><meta><meta charset="utf-8"></head><body> <div>Avant-[<scan id=’info’></scan>]-après.</div> <script>
var my_url="coucou.txt" ;
var pro = fetch(my_url);
pro.then(utiliseResponse);
function utiliseResponse (response) {
if (response.status !== 200) {console.log(’Pb de comm :’+response.status); return}
response.text().then(utilisation);
}
function utilisation (data)
{document.getElementById(’info’).textContent = data ;} </script></body></html>
Résultat avec un fichier coucou.txt qui contient Coucou Michel !: Avant-[Coucou Michel !]-après.
L’écriture précédente est délayée pour montrer les différents objets mis en œuvre. Une écriture moderne mais calabalistique serait :
<!DOCTYPE html><html><head><meta><meta charset="utf-8"></head><body> <div>Avant-[<scan id=’info’></scan>]-après.</div> <script>
fetch("coucou.txt").then(response => {
if (response.status !== 200) console.log(’Pb de comm :’+response.status); else response.text().then(data => {document.getElementById(’info’).textContent = data ;});
});
</script></body></html>
Accès à une image :
C’est un peu plus compliqué car l’objet Response n’a pas de méthode fournissant une image, alors que ci-dessus, pour un texte, nous avons utilisé sa méthode response.text() qui fournissait un string. On utilise la méthode response.blob() qui fournit un objet Promise à partir duquel on accède à l’image via une URL blob, méthode décrite à propos des objets Blob.
Exemple d’affichage d’une image récupérée par un fetch :
<!DOCTYPE html><html><head><meta><meta charset="utf-8"></head><body> <script>
let pro = fetch(’treasuremap.png’).then(utiliseResponse) function utiliseResponse(response) {
if (!response.ok) throw new Error(`erreur HTTP ! statut : ${response.status}`); pro = response.blob();
pro.then(rep => {
let objectURL = URL.createObjectURL(rep);
let image = document.createElement(’img’);
image.src = objectURL ;
document.body.appendChild(image); });
};
</script></body></html>
A la fin du pro.then(..), on peut ajouter un catch qui va traiter les erreurs des deux then : .catch(e => {console.log(’Pb :’ + e.message);});
On peut même mettre avant le catch un 3ème then pour effectuer un traitement postérieur au deux autres (et aisnsi de suite), par exemple :
.then(() => {console.log (’Image affichée !’); return "Coucou"}) .then((str) => {console.log (str);})
.catch((error) => {console.log(’Problème lors du fetch : ‘ + error.message);});
On aura sur la console (qui s’ouvre par Ctrl+ Shift + I) les messages : Image affichée !
Coucou
...
Comme nous l’avons mentionné plus haut, à propos de XMLHttpRequest, dans le cas de données distantes accédées via une url de type http://.., la lecture sur le serveur distant ne peut pas être effectué en javascript qui ne dispose pas de fonction permettant cette lecture. Il faut passer par l’intermédiaire d’un petit programme (en php par exemple) situé sur le serveur de la page en cours qui va effectuer le chargement du fichier distant et qui fournira ensuite à la donnée chargée.
Nous dupliquons ici, l’example précédemment présenté :
<!DOCTYPE html><html><head><meta><meta charset="utf-8"></head><body> <div>Avant-[<scan id=’info’></scan>]-après.</div> <script>
request = new XMLHttpRequest();
var trompeCache = "&bidon="+Math.random()*1000000; request.open("get", "urlget.php?my_url=https://www.llibre.fr/hello.txt"+trompeCache);
request.onload = function()
{ document.getElementById(’info’).textContent = this.responseText ;} request.send(null)
</script>
</body>
</html>
Voici le même exemple avec la méthode fetch/promise : <!DOCTYPE html><html><head><meta><meta charset="utf-8"></head><body>
<div>Avant-[<scan id=’info’></scan>]-après.</div> <script>
var trompeCache = "&bidon="+Math.random()*1000000; my_url = "urlget.php?my_url=https://www.llibre.fr/hello.txt"+trompeCache; var pro = fetch(my_url);
pro.then(utiliseResponse);
function utiliseResponse (response) {
if (response.status == 200)
response.text().then(data =>
{document.getElementById(’info’).textContent = data ;});
}
</script></body></html>
Au lieu de passer my_url en argument à la méthode open de XMLHttpRequest on le passe à la méthode fetch. Dans le cas de XMLHttpRequest il suffit de passer la callback finale de traitement à la méthode onload de la requête.Dans le cas de fetch, il faut la passer à la méthode then l’objet Promise reçu en argument parla callback intermédiaire utiliseResponse. Ouf !!! Évidemment, ensuite on peut chainer les traitements, à condition que chacun d’entre eux renvoie l’objet Promise.
Accès à un fichier du serveur :
<?php
if (isset($_GET['toto'])) {
$data = file_get_contents($_GET['toto']); // Lit le fichier
if ($data !== false) {
echo $data; // idem echo "$data"; avec des guillemets
} else {echo "Echec lecture";}
exit;
}
?>
<!DOCTYPE html><html lang="fr"><head><meta charset="UTF-8"></head>
<body><pre id ='info'></pre>
<script>
fetch('?toto=bidon.js')
.then(r => r.text()) // récupère l'objet texte de la réponse
.then(t => { // traite l'objet texte
document.getElementById('info').textContent = t;
})
.catch(e => alert('Erreur : ' + e));
</script>
</body></html>
Ici dans fetch(url).then(callbackA).then(callbackB).catch(callbacE)
la callbackA(r) est remplacé par la fonction fléchée r => r.text()
la callback(B)(t) est remplacée par la fonction fléchée t => {...}
et la callbackE(e) est remplacée par e => alert('Erreur : ' + e)
Encapsulation dans une fonction asynchrone :
•Avec async/await, vous devez déclarer une fonction avec le mot-clé async pour pouvoir utiliser await à l'intérieur.
•Exemple :
async function fetchData() {
try {
const rep = await fetch('?toto=bidon.txt');
if (!rep.ok) {
throw new Error('Network response was not ok ' + rep.statusText);
}
const contenu = await rep.text();
console.log(contenu);
} catch (erreur) {
console.error('There has been a problem with your fetch operation:', erreur);
}
}
fetchData();
•Lisibilité améliorée :
•Le code avec async/await ressemble à du code synchrone, ce qui le rend plus facile à lire et à comprendre.
•Moins d'imbrications de .then(), ce qui rend le flux de contrôle plus clair.
•Gestion des erreurs simplifiée :
•Utilisation de try...catch pour gérer les erreurs de manière intuitive.
•Exemple :
try {
const rep = await fetch('?toto=bidon.txt');
if (!rep.ok) {
throw new Error('Network response was not ok ' + rep.statusText);
}
const contenu = await rep.text();
console.log(contenu);
} catch (erreur) {
console.error('There has been a problem with your fetch operation:', erreur);
}
1.Compatibilité :
•async/await nécessite un environnement qui supporte ES2017 (ou ES8).
•Pour des environnements plus anciens, .then() peut être nécessaire.
•Projets modernes : Si vous travaillez sur un projet moderne avec un support pour ES2017, async/await est généralement préférable.
•Lisibilité et maintenance : Lorsque vous voulez que votre code soit plus lisible et plus facile à maintenir.
•Gestion des erreurs : Lorsque vous avez besoin d'une gestion des erreurs claire et intuitive.
Exemple :
fetch('?toto=bidon.txt')
.then(response => {
// La promesse est traitée, nous avons une réponse du serveur
if (!response.ok) {
throw new Error('Network response was not ok ' + response.statusText);
}
return response.text(); // Renvoie une nouvelle promesse qui fournira le texte
})
.then(data => {
// La promesse de response.text() est traitée, nous avons le texte
console.log(data); // Affiche le contenu du fichier
})
.catch(error => {
// Si une promesse est rejetée, nous arrivons ici
console.error('There has been a problem with your fetch operation:', error);
});
•Compatibilité : Si vous devez supporter des environnements qui ne supportent pas async/await.
•Opérations simples : Pour des opérations asynchrones simples où l'imbrication de .then() ne pose pas de problème de lisibilité.
Conclusion
Le choix entre async/await et .then() dépend de vos besoins spécifiques. async/await offre une meilleure lisibilité et une gestion des erreurs plus intuitive, mais nécessite une encapsulation dans une fonction asynchrone. .then() est plus flexible et compatible avec des environnements plus anciens, mais peut rendre le code plus difficile à lire avec des imbrications multiples.
La bibliothèque jQuery permet de gérer les proprietés de tous les éléments du DOM qu’on accède soit par la syntaxe jQuery(objet) soit plus simplement par $(objet) ou objet peut être une quelconque des balises HTML (p, div, canvas, blockquote, h2, ul, table....) .
La bibliothèque est accessible sur la page de téléchargement https://releases.jquery.com/jquery/. Elle est disponible sous deux formes (avec des versions plus ou moins anciennes et avec une compatibilité ascendante parfois mauvaise) :
•minified : compressée, sans commentaires dans le code mais plus petite et assez rapide
•uncompressed : standard et modifiable
Il y a également des formes réduites sans ajax et effects : slim (standard) et slim minified (compressée).
Il faut inclure la bibliothèque, généralement dans le <head>...</head>, par exemple par la ligne suivante : <script src='jquery-3.6.0.min.js'></script> qui incluera la version 3.6.0 minifield (à condition de l'avoir télechargée dans le répertoire de la page, mais on peut aussi utiliser celle fournie par jquery :
<script src='http://code.jquery.com/jquery-3.6.0.min.js'></script>
ou par Microsoft (plus lente à télécharger) :
<script src='http://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.6.0.min.js'></script>
ou par google :
<script src='http://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js'></script> (avec une syntaxe différente sur le nom de la librairie).
Normalement toutes les fonctions de jQuery héritent de l'objet racine $. Si cela cause un conflit avec d'autres bibilothèques, on peut renommer cet objet à notre convenance, par exemple myJQ par l'instruction :
myJQ = $.noConflict();
JQuery permet d'accéder à tous les éléments HTML à l'aide du sélecteur $ qui remplace l'ensemble des sélecteurs getBxyzxyz standards de javascript. Ainsi :
•$('elem') renvoie les objets de type 'elem' parmi les types'document', 'body','p', 'div'...
•$('#idp') renvoie l'objet qui a pour identifiant "idp",
•$('.newp') renvoie les objets ayant pour classe 'newp'.
•$('elem[attrib]') renvoie les objets de type 'elem' qui ont l'attribut 'attrib'.
•$('blockquote, .nouveau') renvoie les objets de type blockquote ou de classe nouveau.
Dans ce dernier exemple, on voit qu'on peut mettre plusieurs sélecteurs pour ajouter des éléments dans la sélection.
Les combinaisons de sélecteur de type CSS sont admisses en argument :
+ (voisin direct), ~ (après de même parent), > (enfant direct), ˽ (descendant)...
ATTENTION :
•Quand on assigne l'élément renvoyé à une variable, c'est le premier des objets parmi les objets sélectionnés qui est assigné.
•Quand on applique une méthode, elle est appliquée à tous les objets du tableau sans qu'il soit nécessaire de faire une boucle pour parcourir ce tableau.
REMARQUE :
Dans le cas d'un objet unique comme $('#idp') fournit l'objet jQuery qui permet d'accéder à ses proprités ou d'activer ses méthode, mais on peut également accéder à l'objet javascript de base par :
var myElem = $('#idp')[0];
ou
var myElem = $('#idp').get(0);
ou
var myElem = document.getElementById('idp');
et ensuite accéder aux méthodes de l'objet javascript de base de manière habituelle : myElem.style, myElem.innerHTML...
En plus des méthodes natives javascript de l’élément, jQuery fournit un ensemble de nouvelles méthodes comme les méthodes css(), html(), ready()…
Attention : Modification du nom des détecteurs d’événement
Tous les détecteurs d’événements HTML/javascript tels que onclick, ondblclick, onload, onchange, onkeydown, onmousedown, onfocus, onblur, onkeypress.... sont présents en jQuery sous forme d’une fonction : click(), dblclick(), load(), change(), keydown(), mousedown(), focus(), blur(), keypress()... sans le préfixe on.
L’argument du détecteur n’est pas assigné comme un attribut mais passé en argument à la fonction (je répète : qui a le nom du détecteur HTML/javascript sans le préfixe on). Ainsi, si bjq = $(b) est une variable jQuery associée à un bouton b, et si effacer() est la callback que l’on veut appeler, l’instruction b.onclick = effacer ; est remplacée par bjq.click(effacer);
La méthode css prend en argument les couples attribut à modifier et valeur.
$(’elem’).css(attribut, valeur)
Exemple :
$(’code’).css(’border’, ‘4px solid #f00’)
pour entourer les parties du code (verbatim) d’un cadre de 4px de large rouge.
$(’p’).css(’background-color’, ‘yellow’)
pour mettre le fond des paragraphes (’p’) en jaune.
La méthode css renvoie la valeur de l’attribut (calculée dans le contexte courant) lorsqu’il n’est pas spécifié :
couleur = $(’p’).css(’background-color’)
Dans la dernière ligne de cet exemple on fait une sélection de groupe :
<div id='publicite'>Ceci est une publicité</div>
<p>Ceci est mon <span class='nouveau'>nouveau</span> site web</p>
<script>
$('blockquote').css('background', 'lime')
$('#publicite').css('border', '3px dashed red')
$('.nouveau').css('text-decoration', 'underline')
$('blockquote, #publicite, .nouveau').css('font-weight', 'bold')
</script>
La méthode .html(’...’) joue pour l’objet $(’elem’) le rôle de la méthode innerHTML des objets du DOM javascript. Exemple :
$(’#resultat’).html(’Vous avez cliqué sur le bouton !’)
L’équivalent de la méthode window.onload(callback) qui permet d’exécuter la callback après le chargement de la page est la méthode $(’document’).ready(callback) qui possède la version abrégée suivante $(callback) directement sur l’objet $ lui-même ! Elle est plus précoce et s’exécute même avant la méthode window.onload(callback) dès que le document est prêt à être affiché.
Pour éviter d’exécuter du code jQuery avant que tous les effets des style importés soient appliqués, on peut le placer à la fin de la page HTML et mieux dans une méthode ready (ou sa version abrégée) à la fin de la page HTML.
La méthode click() associée à l’élément sélectionnée par le sélecteur $(’elem’) permet de spécifier la callback à appeler lors d’un clic sur cet élément. Exemple :
<!DOCTYPE html>
<html><head><script src=’jquery-3.6.0.min.js’></script></head> <body>
<button id=’cliquezsurmoi’>Cliquez sur moi</button> <p id=’resultat’>Je suis un paragraphe</p> <script>
$(’#cliquezsurmoi’).click(function()
{ $(’#resultat’).html(’Vous avez cliqué sur le bouton !’)}) </script></body></html>
L’événement keypress est associé à document : $(document).keypress(callback) sans guillemets autour de document. La callback de traitement fournit l’event en argument. Le code reçu de la touche reçu est event.which. On peut le convertir en caractère avec String.fromCharCode(event.which).
Lorsqu’une callback traite un événement, on peut laisser l’événement actif pour qu’il soit traité par d’éventuels autres gestionnaires ou bien l’enlever quand on considère que notre traitement est le seul à devoir être exécuté. Dans ce cas on appelle event.preventDefault() pour supprimer l’éxécution par défaut.
Exemple :
<!DOCTYPE html><html><head>
<script src='jquery-3.6.0.min.js'></script></head>
<body><h2 id="idh2">Appuyez sur une touche du clavier</h2>
<div id='mydiv'></div>
<script>
$(document).keypress(function(event) {
car = String.fromCharCode(event.which)
if (car>='a'&& car<='z' || car>='A'&& car<='Z' || car>='0'&& car<='9')
{ $('#mydiv').html('Touche reçue : ' + car); event.preventDefault();}
})
</script></body></html>
Dans cet exemple, les caractères acceptés sont les 26 lettres de l’alphabet et les 10 chiffres. La callback est anonyme.
Les principaules méthodes associés aux événement de la souris sont mouseup, mousedown, mousemove, mouseenter (dans un élément) mouseleave (d’un élément), mousever, mouseout, mousehover…
Un petit programme de dessin avec jQuery :
<!DOCTYPE html><html><head><script src='jquery-3.6.0.min.js'></script></head><body>
<canvas id='pad' width='480' height='320' style="border:2px solid"></canvas>
<script>
canvas = $('#pad')[0];
context = canvas.getContext("2d");
pendown = false;
$('#pad').mousemove(function(event)
{
var xpos = event.pageX - canvas.offsetLeft
var ypos = event.pageY - canvas.offsetTop
if (pendown) { context.lineTo(xpos, ypos); context.stroke();}
else context.moveTo(xpos, ypos);
})
$('#pad').mousedown(function() { pendown = true } )
$('#pad') .mouseup(function() { pendown = false } )
</script></body></html>
Le même sans jQuery :
<!DOCTYPE html><html><head></head><body>
<canvas id='pad' width='480' height='320' style="border:2px solid"></canvas>
<script>
canvas = document.getElementById('pad');
context = canvas.getContext("2d");
pendown = false;
canvas.onmousemove = function(event)
{
var xpos = event.pageX - canvas.offsetLeft;
var ypos = event.pageY - canvas.offsetTop;
if (pendown) { context.lineTo(xpos, ypos); context.stroke();}
else context.moveTo(xpos, ypos);
};
canvas.onmousedown = function(event) { pendown = true; } ;
canvas.onmouseup = function() { pendown = false } ;
</script></body></html>
Exemple :
<!DOCTYPE html><html> <head>
<script src='jquery-3.6.0.min.js'></script> </head>
<body><h2>Cliquez dans et hors de ces champs</h2>
<input id='premier'> <input> <input> <input>
<script>
$('input').focus(function() { $(this).css('background', '#ff0') } )
$('input').blur(function() { this.style.background = '#aaa' } )
$('#premier').focus()
</script></body></html>
Cette page comporte 4 champs input, et dans jQuery on donne (initialement) le focus au #premier.
Lorsqu’un champ input acqiert le focus il devient jaune (’#ff0’) et quand il le perd il devient gris (’#aaa’). Pour modifier l’attribut du style, la callback focus utilise la méthode jQuery css de $(this) alors que la callback blur utilise directement l’attibut this.style.background de javascript, au libre choix du programmeur.
Remarques :
•Les fonctions de gestion d’événement appelées sans argument callback génèrent l’événement pour l’élément argument de $. Exemple : $(’#premier’).focus().
•Dans la callback, le mot clé this fait référence à l’élément du DOM qui peut être accédé en jQuery par $(this), ou en javascript par directement par this.
On peut se passer de jQuery et compacter le code javascript avec les 3 fonction O(), S(), et C() proposées par Robin Nixon, avec :
•O pour Object (accès à un objet)
•S pour Style (acces au style d’un objet)
•C pour Classe : accès aux objets ayant pour attribut une classe donnée (définie dans la rubrique style par .nonClass {attributs...})
function O(i) {return typeof i == ‘object’ ? i : document.getElementbyId(i)} function S(i) { return O(i).style } function C(i) { return document.getElementsByClassName(i) }
ou ClasName est le nom d'une classe définie dans la rubrique style.
Function O(i) {return typeof i == ‘object’ ? i : document.getElementbyId(i)}
Si l’élément est déjà un objet il est retourné.
Function S(i) { return O(i).style }
(Rappelons que dans la ruvrique style on défini une calsse par : .nomClasse { attributs de style }).
Function C(i) { return document.getElementsByClassName(i) }
Pour utiliser cette fonction sur une sélection d’objets il suffit d’affecter à ces objets un même nom de classe, par exemple lenom, avec par exemple l’attribut class = "lenom". Ensuite la fonction C(’lenom’) va retourner un tableau contenant les éléments qui ont cet attribut : tabelem = C("lenom");
ce qui permettra de faire des modifications sur ses éléments avec une boucle comme suit : for(el of tabelem) S(el).textDecoration = ‘underline’
Un site qui présente les API google et Leaflet : https://www.datavis.fr/index.php#maps
La géolocalisation du client se fait via l'objet navigator.geolocation. qui ne fonctionne que sur des pages obtenues via HTTPS (sécurisé). De plus tous les navigateurs n'offrent pas la fonctionnalité. On teste sa disponinilté par :
if ("geolocation" in navigator) {
/* la géolocalisation est disponible */
} else {
/* la géolocalisation n'est pas disponible */
}
On demande une localisation par l'appel de la méthode :
navigator.geolocation.getCurrentPosition(success[, error[, [options]])
Lacallback success(position) sera appelée lorsque la loc sera obtenue. Elle recevra en argument la localisation dont on extrait les coordonnées position.coords.latitude, position.coords.longitude en degrés. On écrira cette callbak pour traiter la localisation.
La callback error(positionError) sera appelée en cas d'erreur.
L'exemple suivant montre l'utilisation des options facultatives :
var options = {
enableHighAccuracy: true, // false par défaut
timeout: 5000, // infini par défaut (dangereux)
maximumAge: 0
};
function success(pos) {
console.log('Votre position actuelle est :');
console.log(`Latitude : ${pos.coords.latitude}`);
console.log(`Longitude : ${pos.coords.longitude}`);
console.log(`La précision est de ${pos.coords.accuracy} mètres.`);
}
function error(err) {
console.warn(`ERREUR (${err.code}): ${err.message}`);
}
navigator.geolocation.getCurrentPosition(success, error, options);
Remarques :
option.timeout est la durée d'attente maximale (Infinity par défaut)
option.maximumAge concerne la localisation disponible en cache :
•0 (défaut) : elle sera ignorée
•Infinity : elle sera prise systématiquement
•2000 : elle sera prise si aucune loc disponible au bout de 2 secondes.
La méthode
id = navigator.geolocation.watchPosition(success[, error[, options]])
prend exactement les mêmes paramètres que getCurrentPosition, mais la callcback success est appelée chaque fois que la position change. L'id renvoyée sert à arrêter la surveillance du changement de position par l'appel de :
navigator.geolocation.clearWatch(id);
Remarque : la précision est de l'ordre de la dizaine de km ou pire.
Pour l'api Leaflet :
•Référence expliquée : https://leafletjs.com/reference-1.7.1.html
•Quick Start Guide - Leaflet - a JavaScript library for interactive maps (leafletjs.com)
•Tutorials - Leaflet - a JavaScript library for interactive maps (leafletjs.com)
Pour le gpx, il y a un plugin. Voir : https://github.com/mpetazzoni/leaflet-gpx
Les deux lignes suivantes sont à mettre dans le head :
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
crossorigin=""></script>
ATTENTION : Ici la ligne link faisant référence au fichier .css précède la ligne script faisant référence au fichier .js.
ALTERNATIVE trouvé ici https://github.com/mpetazzoni/leaflet-gpx :
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/leaflet.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/leaflet.js"></script>
et pour le gpx :
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-gpx/1.5.1/gpx.min.js"></script>
AUTRE ALTERNATIVE AVEC LES FICHIERS EN LOCAL
J'ai copié en local les fichiers leaflet.css, leaflet.js (+ leaflet.js.map) et gpx.min.js au moyen de wget (en enlevant le s de https) et les scripts fonctionnent avec seulement :
<link rel="stylesheet" href="leaflet.css" />
<script src="leaflet.js"></script>
<script src="gpx.min.js"></script>
Dans le body, une carte peut être chargée et affichée par les instructions suivantes :
<div id="mapid" style="width: 100%; height: 530px;"></div>
<script type="text/javascript">
// Accès aux cartes libres d'openstreetmap
var myLayer = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: 'Map data © <a href="http://www.osm.org">OpenStreetMap</a>'});
var mymap = L.map('mapid', { center: [43.53, 1.57], zoom: 13 });
myLayer.addTo(mymap);
</script>
On remarque qu'on identifie la région (ici une <div>) avec l'identificateur "mapid" qui est ensuite passé en argument au constructeur L.map('mapid').
Remarque : On peut accéder à d'autres fournisseurs de carte, par exemple à mbox pour lequel j'ai une clé (voir plus loin). La définitionde myLayer est un peu plus lourde !! :
var strAttribution = 'Map data ©' +
'<a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>' +
' contributors, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>';
var myLayer =L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}',
{
attribution: strAttribution,
maxZoom: 18,
id: 'mapbox/streets-v11',
tileSize: 512,
zoomOffset: -1,
accessToken: 'pk.eyJ1IjoibGxpYnJlbSIsImEiOiJja3JzNGUwZDQyNzV2MnZubzVlamU5NDh6In0.cR74wJ36iE1vowJbAZUSPA'
});
Les figures (Polygon, Polyline, Circle) héritent de la classe abstraite Path qui hétite elle-même de la classe Layer. Elle apporte les options classiques des tracés (1ère valeur par défaut) :
stroke : true/false
color : "#3388ff"
weight : 3 (largeur en pixels)
opacity : 1.0
lineCap : "round"
lineJoin : "round"
dashArray : null/"5, 2, ..."
dashOffset: null/"3"
fill : ?/true/false
fillColor: ?/"#3388ff"
fillOpacity: 0.2
fillRule: 'evenodd'
bubblingMouseEvents : true/false
et les méthodes suivantes :
redraw() // après un changement de cordonnées par ex,
setStyle(<Path options>
bringToFront() et bringToBack() //monte en haut ou en bas des couches.
Polyline
Création : L.polyline(LatLng[] latlngs , <Path options> ops);
Utile pour tracer un chemin (ou plusieurs chemins si latlngs est un tableau de tableau de LatLng).
Ajoute au Path les options suivantes :
smoothFactor : 1.0 // plus grand pour effectuer un lissage du tracé
noClip : false/true //empêche le clipping (?)
et ajoute les méthodes suivantes :
getLatLngs() =>
setLatLngs(<LatLng[]> latlngs)
isEmpty()
closestLayerPoint(<Point> p) // => Point du Polyline leplus près de p
getCenter() : => le centroid du polyline
getBounds() : => LatLngsBounds [corner1, corner2] avec corner=LatLngs.
addLatLng(<LatLng> latlng, <LatLng[]> latlngs?) Déplace le polyline.
toGeoJSON(precision) :=> représentation GeoJSON du polyline. Exemple :
{
"type": "LineString",
"coordinates": [
[30.0, 10.0], [10.0, 30.0], [40.0, 40.0]
]
}
Exemple de création :
var latlngs = [[lat1, lon1], [lat2, lon2], [lat3, lon3], ...];
var poli = L.polyline(latlngs, {color: 'red', smoothFactor: 1.0});
poli.addTo(mymap);
Polygon
Il étend Polyline. Referme le tracé en joignant le dernier point au premier et en permettant le remplissage du polygone obtenu.
Rectangle : Exemple :
var rectbounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
// creation
L.rectangle(rectbounds, {color: "#ff7800", weight: 1}).addTo(map);
// zoom de la map aux frontuères du rectangle
mymap.fitBounds(rectbounds);
Circle
L.circle([50.5, 30.5], 200, {options}).addTo(mymap);
L.circle([50.5, 30.5], {radius: 200}).addTo(mymap); // Le rayon peut être mis dans les
Méthodes :
setRadius(<Number> radius)
getRadius()
getBounds()
Remarque : Le rayon du cercle varie ensuite lors des zooms effectués sur la carte.
Pour ouvrir un popup à une coordonnées donnée, faire :
var popup = L.popup().setLatLng([51.5, -0.09]).setContent("I am a standalone popup.").openOn(mymap);
Pour ajouter un marker quelque part faire :
var marker = L.marker([51.5, -0.09]).addTo(mymap);
Pour associer un popup à un marker ou un autre élément Leaflet (circle, polygon...) faire :
marker.bindPopup("<b>Hello world!</b><br>I am a popup.");
qui s'ouvrira quand on clique dessus. Pour le positionner ouvert faire :
marker.bindPopup("<b>Hello world!</b><br>I am a popup.").openPopup();
Mieux, pour afficher un Tooltip faire :
marker.bindTooltip("<b>Hello world!</b><br>I am a Tooltip.");
On peut cacher le marker de ce tooltip en définissant son opacité à zéro, à faire à sa déclaration :
var marker = L.marker([51.5, -0.09], {opacity: 0});
marker.bindTooltip("<b>Hello world!</b><br>I am a Tooltip.").addTo(mymap);
Anncienne méthode supplantée par Tooltip : Pour faire apparaitre un popup lors du survol :
// création d'une infoBulle
var infoWindow = L.popup();
// création d'un marker
var oMarker = L.marker([e.latlng.lat, e.latlng.lng]);
// affectation de l'événement au survol
oMarker.on('mouseover', function (e) {
infoWindow.setContent('Le marker est en position :<br>' + e.latlng.toString())
this.bindPopup(infoWindow).openPopup();
});
// affectation de l'événement au sortir
oMarker.on('mouseout', function (e) {
this.togglePopup();
});
On peut ajouter des icons, et il existe un objet divIcon qui hérite de icon qui permet d'insérer du texte encodé en html. Exemple :
var myIcon = L.divIcon({html:"coucou", className: 'my-label', iconAnchor: [10, 30]});
L.marker([43.546, 1.567], {icon: myIcon}).addTo(mymap);
Exemple ajout d'un nombre entouré d'un cercle (dont le rayon ne variera lors des zooms effectués sur la carte, contrairement à l'utilisation d'un L.circle) :
<style>
/* définition du cercle par rapport au point d'ancrage */
#my-circle{
position: absolute;
border-style: solid;
border-width: 3;
border-color: #38f;
border-radius: 12px;
width: 20px;
height: 20px;
transform: translate(-10px, -22px);
}
/* définition du texte par rapport au centre */
#my-text{
position: relative;
top:0px;
left:6px;
color: #f0f;
font-size:14px;
}
</style>
</script>
// Ajout du numéro "5" au point choisi
var str = '<div id="my-circle"><div id="my-text">5</div></div>';
var myIcon = L.divIcon({html:str, iconAnchor: [0, 0]});
L.marker([43.5387, 1.5364], {icon: myIcon}).addTo(map);
</script>
Donne accès à des cartes et est basé sur Leaflet dont il fournit un surensemble.
Inscription à Mapbox : l.m@W : KimCarle27, nom visible : llibrem
Default public token
pk.eyJ1IjoibGxpYnJlbSIsImEiOiJja3JzNGUwZDQyNzV2MnZubzVlamU5NDh6In0.cR74wJ36iE1vowJbAZUSPA
Pour utiliser l'api mapbox mapbox-gl.js voir explications : Web applications | Help | Mapbox
Le fournisseur de services Mapbox maintient aussi l'API leaflet .js qui est aussi utilisable dans Mapquest.
Etant plus complexe que leaflet.js, je ne décrit que le minimum trouvé ici : https://docs.mapbox.com/mapbox-gl-js/api/#quickstart
Ajouter les 2 lignes suivantes dans le head :
<script src='https://api.mapbox.com/mapbox-gl-js/v2.3.1/mapbox-gl.js'></script>
<link href='https://api.mapbox.com/mapbox-gl-js/v2.3.1/mapbox-gl.css' rel='stylesheet' />
Puis ceci dans le body :
<div id='mymap' style='width: 400px; height: 300px;'></div>
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoibGxpYnJlbSIsImEiOiJja3JzNGUwZDQyNzV2MnZubzVlamU5NDh6In0.cR74wJ36iE1vowJbAZUSPA';
var map = new mapboxgl.Map({
container: 'mymap',
style: 'mapbox://styles/mapbox/streets-v11',
center: [1.57, 43.53], // starting position [lng, lat]
zoom: 10 // starting zoom
});
</script>
</body>
L'api de cartographie Mapquest offre de nombreux services : recherche d'adresses, d'itinéraires, etc... J'ai testé. Il semble que ces services vont bien au-dela de ce dont j'ai besoin et que Leaflet soit bien suffisant.
MapQuest Developer Network | Mapping, Geocoding, Routes, Traffic | MapQuest Developer Network
Pour accéder aux services Mapquest il faut créer un compte. A l'issue de cette inscription on obtient la clé qui permet l'accès aux services.
Inscription : l.m@W : KimCarle27, nom visible : llibrem
Ma première clé :
Consumer Key : uuSJMssOxLQMWRjVMF5MZzmGHAKvCQVO
Consumer Secret : e3fZ5TPmEiAFrGNd
Key Issued : Wed, 07/28/2021 - 10:45
Key Expires : Never
Documentation : MapQuest JavaScript Library — v1.3 | MapQuest API Documentation
référence : MapQuest JavaScript Library — v1.3 - L.mapquest.map | MapQuest API Documentation
exemples : MapQuest JavaScript Library — v1.3 - Map with MapQuest Control | MapQuest API Documentation
Avant d'utiliser les API, il faut spécifier les deux fichiers API javascript (.js) et feuille de style (.css) utilisés dans le head. Si on se limite à un tracé de carte, il s'agit de mapquest-maps (.js et .css) qui sont des versions allégées des fichiers plus complet mapquest (.js et .css). On les incorpore par les 2 lignes suivantes placées dans le <head >:
<script src="https://api.mqcdn.com/sdk/mapquest-js/v1.3.2/mapquest.js"></script>
<link type="text/css" rel="stylesheet" href="https://api.mqcdn.com/sdk/mapquest-js/v1.3.2/mapquest.css"/>
Ensuite, les instructions d'affichage de la carte peuvent être :
1.dans une zone <script> située dans la zone <body>
2.dans la zone <head>, après les 2 lignes précédentes. Dans ce deuxième cas on mettra ces instructions dans le corps de la callback suivante :
window.onload = function() { /* .. instruction mapquest .. */ }
La première instruction évoquant une API Mapquest doit être :
L.mapquest.key = 'uuSJMssOxLQMWRjVMF5MZzmGHAKvCQVO';
La méthode configurant et créant une carte est :
var myMap = L.mapquest.map('element', option);
•'element' est généralement l'identificateur donné à une zone
<div id="element" style="width: 100%; height: 530px;"></div>
dans laquelle sera tracée la carte,
•option est un objet json contenant les paramètres de la carte à tracer dont les principaux sont :
{
center: [lat, lng],
layers: L.mapquest.tileLayer('map'),
zoom: 12
}
Contrôles de choix du fond de carte
L'attribut layers définir le fond de carte. Il est de la forme L.mapquest.tileLayer('map'), et 'map' est le choix standard habituel, mais il y a aussi :
•'map' cartes standard avec routes
•'light' idem plus claires
•'dark' mais sombres
•'satellite' vue satellite
•'hybrid' idem + routes
On peut installer un bouton de contrôle pour choisir le fond de carte en appelant la méthode : L.control.layers(options).addTo(myMap);
où options est un objet json décrivant les différents choix :
L.control.layers({ 'Map': L.mapquest.tileLayer('map'), 'Hybrid': L.mapquest.tileLayer('hybrid'), 'Satellite': L.mapquest.tileLayer('satellite'), 'Light': L.mapquest.tileLayer('light'), 'Dark': L.mapquest.tileLayer('dark') }).addTo(myMap); |
|
Contrôles de navigation
L'instruction suivante : myMap.addControl(L.mapquest.control()); ajoute des controles de navigation à la carte :
|
|
Exemple avec le Leaflet Draw plugin
Exemple : MapQuest JavaScript Library — v1.3 - Map with Leaflet Draw | MapQuest API Documentation
<html>
<!-- UTILISATION DU Leaflet Draw plugin -->
<head>
<script src="https://api.mqcdn.com/sdk/mapquest-js/v1.3.2/mapquest.js"></script>
<script src="https://unpkg.com/leaflet-draw@0.4.13/dist/leaflet.draw.js"></script>
<link type="text/css" rel="stylesheet" href="https://api.mqcdn.com/sdk/mapquest-js/v1.3.2/mapquest.css"/>
<link type="text/css" rel="stylesheet" href="https://unpkg.com/leaflet-draw@0.4.13/dist/leaflet.draw.css"/>
<script type="text/javascript">
window.onload = function () {
L.mapquest.key = 'lYrP4vF3Uk5zgTiGGuEzQGwGIVDGuy24';
var baseLayer = L.mapquest.tileLayer('map');
var map = L.mapquest.map('map', {
center: [29.953745, -90.074158],
layers: baseLayer,
zoom: 10
});
var drawnItems = L.featureGroup().addTo(map);
map.addControl(new L.Control.Draw({
edit: {
featureGroup: drawnItems,
poly: {allowIntersection: false}
},
draw: {
polygon: {allowIntersection: false, showArea: true}
}
}));
map.on(L.Draw.Event.CREATED, function (event) {
var layer = event.layer;
drawnItems.addLayer(layer);
})
}
</script>
</head>
<body style="border: 0; margin: 0;">
<div id="map" style="width: 100%; height: 530px;"></div>
</body>
</html>
Cet exemple incorpore les contrôles ci-contre qui permettent de tracer des segments, des polygones, des rectangles, des cercles, des balises, des marques circulaires, et de les éditer et de les supprimer (éventuellement). |
|
La doc de Leaflet dans Mapquest laisse à désirer : https://developer.mapquest.com/demos/leaflet
Voir chapitre 24.2 l'utilisation de Leaflet sans Mapquest.
API permettant de localiser un lieu à partir de son adresse
API permettant de tracer des itinéraires
Voici les cartes que j'utilise dans visutracesgpx.php.
// Différents fonds considérés
var carteIGN = L.tileLayer('https://data.geopf.fr/wmts?'+
'&REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&TILEMATRIXSET=PM'+
'&LAYER={ignLayer}&STYLE={style}&FORMAT={format}'+
'&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}',
{
ignLayer: 'GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2',
style: 'normal',
format: 'image/png',
service: 'WMTS',
/*opacity: 1.0,*/
attribution: 'Carte © IGN/Geoplateforme'
});
var mapnik = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
});
var osm_fr = L.tileLayer('https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png', {
maxZoom: 20,
attribution: '© OpenStreetMap France | © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
});
var EMaj = L.tileLayer('http://tile.mtbmap.cz/mtbmap_tiles/{z}/{x}/{y}.png', {
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> & USGS'});
var openTopoMap = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
maxZoom: 17,
attribution: 'Map data: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: © <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
});
var Esri_WorldImagery = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
});
var mapboxAttribution = 'Map data ©' +
'<a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>' +
' contributors, Imagery ©<a href="https://www.mapbox.com/">Mapbox</a>';
var mytok = 'pk.eyJ1IjoibGxpYnJlbSIsImEiOiJja3JzNGUwZDQyNzV2MnZubzVlamU5NDh6In0.cR74wJ36iE1vowJbAZUSPA';
var mapboxUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}';
var mb_plan = L.tileLayer(mapboxUrl, {
id: 'mapbox/streets-v11',
tileSize: 512,
zoomOffset: -1,
attribution: mapboxAttribution,
accessToken: mytok
});
var mb_sats = L.tileLayer(mapboxUrl, {
id: 'mapbox/satellite-v9',
tileSize: 512,
zoomOffset: -1,
attribution: mapboxAttribution,
accessToken: mytok
});
var ignSat = L.tileLayer('https://data.geopf.fr/wmts?'+
'&REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&TILEMATRIXSET=PM'+
'&LAYER={ignLayer}&STYLE={style}&FORMAT={format}'+
'&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}',
{
ignLayer: 'ORTHOIMAGERY.ORTHOPHOTOS',
style: 'normal',
format: 'image/jpeg',
service: 'WMTS'
});
// Objet descripeur choix des fonds
var baseMaps = {
"Mapnik": mapnik,
"OSM_FR": osm_fr,
"EMaj" : EMaj,
"OpenTopoMap": openTopoMap,
"IGN_std" : carteIGN,
"MapBox_plan": mb_plan,
"Esri W.I.": Esri_WorldImagery,
"MapBox_sats": mb_sats,
"IGN sats" : ignSat
};
baseMaps.IGN_std.addTo(mymap);
// Ajout controle choix fond
L.control.layers(baseMaps).addTo(mymap);
Soit a écrire une petite calculette 4 opérations en JavaScript. J’ai trouvé un code HTML de calculette, assez simple au niveau algorithmique (on laisse à JavaScript le soin de trouver les calculs à effectuer et de les faire). On ne gère que la mise en place du clavier, la lecture des touches. Voici l’aspect de la calculette.
Voici le programme original (fichier calculette.html) :
<html>
<head>
<script>
function concatene(val) { // Concatène les caractères tapés document.getElementById("output").value += val
}
function evaluer() { //Evalue l’entrée globale et renvoie le résultat let ecran = document.getElementById("output"); ecran.value = eval(ecran.value);
}
function effacer() {//E fface l’écran document.getElementById("output").value = ""
}
</script>
<style>td button{width :100 %}</style>
</head>
<body>
<table border>
<tr> <!-- Première ligne avec la zone résultat --> <td colspan="3"><input id="output"/></td> <td><button onclick="effacer()">c</button></td>
</tr>
<tr> <!-- Deuxième ligne →
<td><button onclick="concatene(’1’)">1</button></td> <td><button onclick="concatene(’2’)">2</button></td> <td><button onclick="concatene(’3’)">3</button></td> <td><button onclick="concatene(’+’)">+</button></td>
</tr>
<tr> <!-- Troisième ligne →
<td><button onclick="concatene(’4’)">4</button></td> <td><button onclick="concatene(’5’)">5</button></td> <td><button onclick="concatene(’6’)">6</button></td> <td><button onclick="concatene(’-’)">-</button></td>
</tr>
<tr> <!-- Quatrième ligne →
<td><button onclick="concatene(’7’)">7</button></td> <td><button onclick="concatene(’8’)">8</button></td> <td><button onclick="concatene(’9’)">9</button></td> <td><button onclick="concatene(’*’)">*</button></td>
</tr>
<tr> <!-- Cinquième ligne →
<td><button onclick="concatene(’.’)">.</button></td> <td><button onclick="concatene(’0’)">0</button></td> <td><button onclick="evaluer()">=</button></td> <td><button onclick="concatene(’/’)">/</button></td>
</tr>
</table>
</body>
</html>
La fonction concatene(val) prend le caractère tapé (val) et le concatène avec les caractères tapés précédents à l’intérieur de la zone d’édition crée par <input id="output"/>.
La fonction evaluer() lit le texte à l’intérieur de cette et sous-traite à JavaScript le soit d’évaluer le résultat qui remplace ensuite le texte.
La fonction effacer() vide la zone d’édition.
La ligne <style>td button{width :100 %}</style> est du CSS. Elle indique que les boutons doivent s’étendre pour occuper tout l’espace disponible (par défaut ils font juste la taille nécessaire pour afficher leur contenu).
Ensuite dans le body on met une table entouré d’une bordure : <table border>
...
</table>
On y ajoute 5 lignes : 5 fois <tr> .... </tr>
Dans les lignes 2 à 5 on met chaque fois 4 cellules <td>...</td> alors que dans la première on en met que 2, mais la première occupe 3 places (colspan="3") avec dans laquelle on affichera le résultat : <input id="output"/>
Dans toutes les autres cellules on met un bouton (<button ...>X</button>) qui contient le caractère X parmi (1,2,...0,=,/) et on lui associe une fonction à exécuter (callback) au moyen de l’attribut onclick=.…
Le projet JavaScript consiste à réaliser cette calculette dans un programme en pur JavaScript : calculetteJS.js, ce qui permettra de l’exécuter avec EXE_JS.HTML. En plus on modifiera la fonction effacer pour qu’elle n’efface que la dernière touche entrée ce qui permet de corriger les erreurs de frappe sans tout perdre.
Voici une solution :
// Concaténation du caractère tapé dans l’écran function concatene(val) { ecran.value += val ;} // Evaluation la chaîne des opérations et renvoi du résultat function evalue() { ecran.value = eval(ecran.value); } // Efface le dernier caractère de l’écran
var efface = function() {
var txt = ecran.value ;
var n = txt.length ;
if (n > 0) ecran.value = txt.substring(0 , n-1);
}
var b, c, r, t ;
var opr = ["c", "+", "-", "*", "/"]; // Touches colonne de gauche var bas = [".", "0", "="]; // Touches ligne du bas t = document.createElement(’table’); // La grille qui contient les touches t.style.borderStyle = "double" ;
var ligne = 0 ;
var colon = 0 ;
r = t.insertRow(ligne); // La ligne du haut var ecran = document.createElement(’input’); // L’écran ecran.style.textAlign = "right" ;
c = r.insertCell(colon); // La cellule qui contiendra l’écran c.colSpan = 3 ; // s’étendra sur 3 colonnes c.appendChild(ecran); // on y met l’écran dedans b = document.createElement(’button’); // Un bouton b.style.width = "100 %" ; // qui occupe tout son espace b.innerHTML = opr[ligne]; // contenant opr[0] = "c" b.onclick = efface ; // qui servira pour effacer() c = r.insertCell(++colon); // on crée une cellule dans la colonne suivante c.style.borderStyle = "solid" ; // avec un trait autour c.appendChild(b) // on ajoute le bouton dans cette cellule var k = 1 ; // Numéro du chiffre sur la touche for (ligne = 1 ; ligne < 5 ; ligne++) { // On va ajouter les lignes n° 1,2,3,4 r = t.insertRow(ligne); // ajout de la ligne for (colon = 0 ; colon < 4 ; colon++) { // On va y mettre les colonnes n° 0,1,2,3 b = document.createElement(’button’); // on crée un bouton b.style.width = "100 %" ; // qui occupera tout l’espace de la cellule let op ;
if (colon == 3) op = opr[ligne];// avec symbole si colonne de droite else {
if (ligne < 4) op = k++; // avec chiffre 1...9 else op = bas[colon]; // ou ., 0, = en dernière ligne
}
b.innerHTML = op ;
b.onclick = function () {concatene(op);}; // callback clic sur le bouton c = r.insertCell(colon); // on crée la cellule c.style.borderStyle = "solid" ;
c.appendChild(b) // on y met le bouton if (ligne == 4 && colon == 2) b.onclick = evalue ;
}
}
// On ajoute la table au document
document.body.appendChild(t); // ajoute à la suite
EXPLICATIONS :
Les caractères "c", "+", "-", "*", "/" associés aux touches de gauche sont définis dans le tableau opr.
Les caractères associés ".", "0", "=" associés aux touches du bas sont définis dans le tableau bas.
On crée ensuite la table t avec un cadre double.
On crée une ligne n°0 dans la table nommé r.
On crée une zone d’entrée/sortie input que l’on met dans la première cellule de la ligne, étalée sur 3 colonnes.
On crée un bouton b, affecté du caractère "c" et de la callback efface si onclick.
On ajoute une cellule dans la ligne dans laquelle on insère le bouton, ce qui termine la première ligne.
Les quatre lignes suivantes sont traitées dans une boucle qui insère une nouvelle ligne à chaque fois.
Dans chaque ligne on insère 4 cellules contenant chacune un bouton. Des tests permettent de différencier les boutons qui reçoivent les chiffres 1, 2...9 par le biais de la variable k, de ceux qui reçoivent les caractères de la dernière colonne ou dernière ligne. On utilise la variable op pour déterminer le caractère qui est mis dans les boutons.
Pour tous ces boutons la callback à appeler si onclick est concatene(op) sauf pour le "=" (ligne == 4 && colon == 2) où il faut appeler evalue.
Tout à la fin on ajoute la table t au document (en fait l’ordre importe peu, on aurait pu l’ajouter dès la définition de la variable t). On remarquera également le fait que des fonctions utilisent la variable ecran qui est visible dans tout le document, définie par un var plus bas dans le fichier.
Le texte tapé dans un textarea est écouté par un listener de ‘input’. La callback émet un customEvent qui prend en detail le contenu du textarea et qui peut remonter aux parents. On ajoute un listener à la form, parente de l’input qui reçoit ces données et les envoie sur la console.
<!DOCTYPE html>
<title>source javascript</title>
</head>
<body>
<form>
<textarea></textarea>
</form>
<script>
const form = document.querySelector(’form’); const textarea = document.querySelector(’textarea’);
// Create a new event, allow bubbling, and provide any data you want to pass // to the "detail" property
const eventAwesome = new CustomEvent(’awesome’, { bubbles : true,
detail : { prise : () => textarea.value }
});
// The form element listens for the custom "awesome" event // and then consoles the output of the passed text() method form.addEventListener(’awesome’, e => console.log(e.detail.prise()));
// As the user types, the textarea inside the form dispatches/triggers // the event to fire, and uses itself as the starting point textarea.addEventListener(’input’, e => e.target.dispatchEvent(eventAwesome));
</script>
</body>
</html>
Même exemple plus compliqué où l’événement est créé anonymement et dynamiquement au moment du dispatch :
<!DOCTYPE html>
<title>source javascript</title>
</head>
<body>
<form>
<textarea></textarea>
</form>
<script>
const form = document.querySelector(’form’); const textarea = document.querySelector(’textarea’);
form.addEventListener(’awesome’, e => console.log(e.detail.text()));
textarea.addEventListener(’input’, function() { // Create and dispatch/trigger an event on the fly // Note : Optionally, we’ve also leveraged the "function expression" (instead of the "arrow function expression") so "this" will represent the element this.dispatchEvent(new CustomEvent(’awesome’, { bubbles : true, detail : { text : () => textarea.value } }))
});
</script>
</body>
</html>
Autre exemple de simulation d’un clic dans un checkbox, en appuyant sur un bouton. 2 autres boutons permettent d’interdire ou de re-autoriser cette action.
<!DOCTYPE html>
<title>source javascript</title>
</head>
<body>
<input type="checkbox" id="checkbox"/><label for="checkbox">Checkbox</label> <input type="button" onclick="simulateClick();" value="Simulate click"/> <input type="button" onclick="addHandler();" value="Add a click handler that calls preventDefault"/> <input type="button" onclick="removeHandler();" value="Remove the click handler that calls preventDefault"/>
<script>
function preventDef(event) {
event.preventDefault();
}
function addHandler() {
document.getElementById("checkbox").addEventListener("click", preventDef, false);
}
function removeHandler() {
document.getElementById("checkbox").removeEventListener("click", preventDef, false);
}
function simulateClick() {
var evt = document.createEvent("MouseEvents"); evt.initMouseEvent("click", true, true, window,
0, 0, 0, 0, 0, false, false, false, false, 0, null); var cb = document.getElementById("checkbox"); var canceled = !cb.dispatchEvent(evt);
if(canceled) {
// A handler called preventDefault
alert("canceled");
} else {
// None of the handlers called preventDefault alert("not canceled");
}
}</script>
</body>
</html>
Voici un jeu graphique initialement écrit en Python, traduit en html et javscript. Le labyrinthe est décrit par un fichier texte, et les éléments du décor sont décrits par des images de format GIF qui sont dans le même répertoire que la page html. L’accès en lecture au fichier texte et aux images est un peu laborieuse par rapport au programme Python, mais le reste du code est très proche.
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"/></head>
<body>
<!--
La scène du jeu est un labyrinthe avec éventuellement un trèsor et une clé pour prendre le trésor, des portes de couleur avec des clés de couleurs assorties pour les ouvrir, et un personnage que l’on déplace à l’aide des flèches. Il faut pousser la bonne clé vers sa porte ou son coffre. Le jeu est gagné qu&& il n’y a plus de portes ni de coffre.
Un tableau (liste de liste de caractères) tabScene est utilisé pour mémoriser l’état du jeu :
- le caractère espace représente une case vide - la lettre M un mur
- la lettre v une clé verte
- la lettre b une clé bleue
- la lettre r une clé Rouge
- la lettre c une clé de coffre
- la lettre V une porte verte
- la lettre B une porte bleue
- la lettre R une porte rouge
- la lettre C un coffre aux trésors
- la lettre X pour la position du personnage Au début du jeu la description de la scène pour le niveau du jeu en cours est lue dans le fichier niveaux.lvl qui comporte actuellement 3 scènes pour 3 niveaux de jeu.
Chaque scène est décrite par NBL+1 lignes : - la première ligne comporte un nombre qui est le niveau de la scène suivi du nombre de lignes NBL et du nombre de colonnes NBC.
- Chacune des NBL lignes suivantes correspond à une ligne de la scène qui sera dessinée à l’écran. Chaque ligne comporte NBC caractères pour les NBC blocs de la ligne. Les caractères suivants sur la ligne n’ont pas de signification.
Si le ou les derniers blocs d’une ligne sont des espaces, pour être sur qu’ils soient bien présents (alors qu’on ne voit rien) et que la ligne ne se termine pas avant, NBC+1ième position et plus il faut mettre un caracyère quelconque par exemple un point (.) ce qui permet de bien visualiser la fin de la ligne.
Lorsque le joueur a gagné le premier niveau, le niveau suivant est lu et ainsi de suite. Si le jouer se bloque, il peut recommencer le niveau en cours en appuyant sur la lettre r ou R.
-->
<!-- Images virtuelles pour charger les vignettes --> <img id=’cleverte’ src="cleverte.gif" style=’display :none’> <img id=’clebleue’ src="clebleue.gif" style=’display :none’> <img id=’clerouge’ src="clerouge.gif" style=’display :none’> <img id=’cleor’ src="cleor.gif" style=’display :none’> <img id=’mur’ src="pierre.gif" style=’display :none’> <img id=’porteverte’ src="porteverte.gif" style=’display :none’> <img id=’portebleue’ src="portebleue.gif" style=’display :none’> <img id=’porterouge’ src="porterouge.gif" style=’display :none’> <img id=’coffre’ src="coffre.gif" style=’display :none’> <img id=’persobas’ src="persobas.gif" style=’display :none’> <img id=’persohaut’ src="persohaut.gif" style=’display :none’> <img id=’persogauche’ src="persogauche.gif" style=’display :none’> <img id=’persodroite’ src="persodroite.gif" style=’display :none’> <!-- Input pour lecture fichier descripteur du labyrinthe --> <input id="inLab" type="file">
<p id="etat" style="text-align :center">Cliquer sur le bouton ci-dessus pour choisir le fichier "niveau.lvl" décrivant le labyrinthe</p>
<div style = "text-align :center ;"><canvas id="canevas"></canvas></div>
<script>
// LE PROGRAMME EN JAVASCRIPT
// Les variables associées aux vignettes
const cleverteElm = document.getElementById("cleverte"); const clebleueElm = document.getElementById("clebleue"); const clerougeElm = document.getElementById("clerouge"); const cleorElm = document.getElementById("cleor"); const murElm = document.getElementById("mur"); const porteverteElm = document.getElementById("porteverte"); const portebleueElm = document.getElementById("portebleue"); const porterougeElm = document.getElementById("porterouge"); const coffreElm = document.getElementById("coffre"); const persobasElm = document.getElementById("persobas"); const persohautElm = document.getElementById("persohaut"); const persogaucheElm = document.getElementById("persogauche"); const persodroiteElm = document.getElementById("persodroite"); const inLab = document.getElementById("inLab"); const etat = document.getElementById(’etat’); // Déclaration de la fenêtre et de son canevas const canvas = document.getElementById(’canevas’); canvas.style.background = ‘green’ ;
//-- Bouton pour sélectionner le fichier décrivant le labyrinthe ---- var strLabyr = null ; // Variable stockant la description des labyrinthes inLab.onchange = loadLabyr ; // callback appelée pour lire le fichier function loadLabyr() {
if (this.files[0].length == 0) return null ; let reader = new FileReader();
reader.readAsText(this.files[0]);
reader.onload = function()
{
inLab.style = ‘display :none’ ;
etat.textContent="Taper r ou R pour recommencer le niveau" ; strLabyr = reader.result ;
nouveauLab();
}
}
// Initialisations générales
var niveau = 1 ; // niveau initial du jeu
const cote = 50 ; //Largeur des blocs images élémentaires (bloc de mur,...) var nbl, nbc ;
var tabScene ;
// Classe mémorisant l’état du personnage ---------------------------- function Perso() {
this.x = 0 ; // Inutile : pour signaler l’existence de ce membre this.y = 0 ; // Inutile : pour signaler l’existence de ce membre this.bas = persobasElm ;
this.haut = persohautElm ;
this.gauche = persogaucheElm ;
this.droite = persodroiteElm ;
}
//--------------------------------------------------------------------
var perso = new Perso(); // On crée un personnage que l’on va déplacer
/*-----------------------------------------------------------------------
Lecture du string strLabyr contenant le descript des labyrinthes jusqu’à ce qu’on trouveau celui qui correspond au "niveau".
On lit alors ce niveau et on met dans le tableau tabScene les vignettes des images correspondantes puis on l’affiche sur le canevas
*/
function nouveauLab() {
const line = strLabyr.split(/\r\n|\n/);
let nl = 0 ;
let n = 1 ;
while (n <= niveau)
{
let vals = line[nl++].split(’ ‘);
let code = parseInt(vals[0]);
if (isNaN(code)) { alert("GAGNE. Vous avez atteint le dernier niveau disponible."); return}
nbl = parseInt(vals[1]); // Nb de lignes if (code != niveau) {nl += nbl ;}// on saute ce niveau else {
// Initialisation du tableau décrivant la scène tabScene = new Array(nbl)
nbc = parseInt(vals[2]); // Nb de colonnes canvas.width = nbl*cote ;
canvas.height = nbc*cote ;
for (i = 0 ; i < nbl ; i++)
{
tabScene[i] = new Array(nbc);
code = line[nl++];
for (j = 0 ; j < nbc ; j++) {
tabScene[i][j] = code[j];
if (code[j] == ‘X’) { //création et position initiale du personnage (bras vers le bas)
perso.x = j
perso.y = i
perso.img = perso.bas
}
}
}
}
n++;
}
afficher();
}
/* ---------------------------------------------------------------------
Affichage des vignettes du tableau tabScene sur le canevas
*/
function afficher()
{
var ctx = canvas.getContext("2d");
// D’abord on efface la version précédente ctx.clearRect(0,0, canvas.width, canvas.height); for(let i = 0 ; i < nbc ; i++) {
for(let j = 0 ; j < nbl ; j++) {
if (tabScene[j][i]==’ ‘) continue ;
else if (tabScene[j][i]==’M’) {
ctx.drawImage(murElm, i*cote,j*cote);
}
else if (tabScene[j][i]==’C’) {
ctx.drawImage(coffreElm, i*cote,j*cote);
}
else if (tabScene[j][i]==’c’) {
ctx.drawImage(cleorElm, i*cote,j*cote);
}
else if(tabScene[j][i]==’v’) {
ctx.drawImage(cleverteElm, i*cote,j*cote);
}
else if(tabScene[j][i]==’b’) {
ctx.drawImage(clebleueElm, i*cote,j*cote);
}
else if(tabScene[j][i]==’r’) {
ctx.drawImage(clerougeElm, i*cote,j*cote);
}
else if(tabScene[j][i]==’B’) {
ctx.drawImage(portebleueElm, i*cote,j*cote);
}
else if(tabScene[j][i]==’R’) {
ctx.drawImage(porterougeElm, i*cote,j*cote);
}
else if(tabScene[j][i]==’V’) {
ctx.drawImage(porteverteElm, i*cote,j*cote);
}
else if(tabScene[j][i]==’X’) {
ctx.drawImage(perso.img, perso.x*cote,perso.y*cote);
}
}
}
}
// Tests si présentation bonne clé devant sa porte -------------------------------- function cleVerteDevantPorteVerte(posx,posy,dx,dy) { if ((tabScene[posy+dy][posx+dx]==’v’) && (tabScene[posy+2*dy][posx+2*dx]==’V’)) return true ;
else
return false ;
}
function cleRougeDevantPorteRouge(posx,posy,dx,dy) { if ((tabScene[posy+dy][posx+dx]==’r’) && (tabScene[posy+2*dy][posx+2*dx]==’R’)) return true ;
else
return false ;
}
function cleBleueDevantPorteBleue(posx,posy,dx,dy) { if ((tabScene[posy+dy][posx+dx]==’b’) && (tabScene[posy+2*dy][posx+2*dx]==’B’)) return true ;
else
return false ;
}
// Test si clé dorée poussée devant le coffre --------------------------------------
function cleOrDevantCoffre(posx,posy,dx,dy) { if ((tabScene[posy+dy][posx+dx]==’c’) && (tabScene[posy+2*dy][posx+2*dx]==’C’)) return true ;
else
return false ;
}
// Test si clé poussée vers un espace libre ------------------------------------------
function cleDevantVide(posx,posy,dx,dy) {
if((tabScene[posy+dy][posx+dx]==’v’ ||
tabScene[posy+dy][posx+dx]==’b’ ||
tabScene[posy+dy][posx+dx]==’r’ ||
tabScene[posy+dy][posx+dx]==’c’) && (tabScene[perso.y+2*dy][perso.x+2*dx]== ‘ ‘))
return true ;
else
return false ;
}
// Test si la position est à l’intérieur du jeu-------- function aLinterieur(posx,posy) {
if (posx >=0 && posy >=0 &&
posx<=nbc-1 && posy <=nbl-1)
return true ;
else
return false ;
}
// Fonction gérant le mouvement du personnage ---------------------------------------------------------
function avance(xdirection, ydirection) {
let avancePerso = false ;
if (aLinterieur(perso.x+xdirection, perso.y+ydirection)) { // Le mouvement est a l’intérieur du jeu
if (tabScene[perso.y+ydirection][perso.x+xdirection]==’ ‘) // vide devant : le personnage peut avancer
avancePerso = true ;
// sinon il y a un objet devant le personnage else if (aLinterieur(perso.x+2*xdirection, perso.y+2*ydirection)) { // et après ce n’est pas la frontière du jeu // pousse-t-on une clé devant sa porte if (cleVerteDevantPorteVerte(perso.x,perso.y,xdirection,ydirection) || cleRougeDevantPorteRouge(perso.x,perso.y,xdirection,ydirection) || cleBleueDevantPorteBleue(perso.x,perso.y,xdirection,ydirection) || cleOrDevantCoffre(perso.x,perso.y,xdirection,ydirection)) { // Oui c’est le cas tabScene[perso.y+ydirection][perso.x+xdirection]=’ ‘; // On enlève la clé tabScene[perso.y+2*ydirection][perso.x+2*xdirection]=’ ‘;// On enlève la porte ou le coffre avancePerso = true ; // On avance le personnage } // Non, pousse-t-on une clé devant le vide ? else if (cleDevantVide(perso.x,perso.y,xdirection,ydirection)) { // oui c’est le cas, on déplace la clé puis le personnage tabScene[perso.y+2*ydirection][perso.x+2*xdirection] = tabScene[perso.y+ydirection][perso.x+xdirection]; avancePerso = true ; // On autorise l’avance
}
}
}
// déplace perso
if (avancePerso == true) {
tabScene[perso.y][perso.x]=’ ‘ // On enlève le personnage perso.x = perso.x + xdirection ;
perso.y = perso.y + ydirection ;
tabScene[perso.y][perso.x]=’X’ ; // On replace le personnage
}
afficher()
// si gagne ? niveau suivant
let gagne = true ;
for (let i= 0 ; i < nbl ; i++) {
for (let j=0 ; j < nbc ; j++) {
if (tabScene[i][j]==’C’) {gagne = false ; break}
}
if (gagne == false) break
}
if (gagne == true){
alert("Vous avez-gagné au niveau "+niveau); niveau= niveau+1 ;
nouveauLab();
afficher();
}
}
// Gestion des touches de l’ordinateur
document.addEventListener(’keydown’, monAction); function monAction(e) {
switch (e.key) {
case ‘r’ :
case ‘R’ : alert("On recommence !"); nouveauLab(); afficher(); break ; case ‘ArrowDown’ : perso.img = perso.bas ; avance( 0, 1); break ; case ‘ArrowUp’ : perso.img = perso.haut ; avance( 0,-1); break ; case ‘ArrowLeft’ : perso.img = perso.gauche ; avance(-1, 0); break ; case ‘ArrowRight’ : perso.img = perso.droite ; avance( 1, 0); break ;
}
}
</script>
</body>
</html>
1Il y a les langages compilés (comme le C, C++...) dont le texte doit être préalablement traduit en langage machine (on dit compilé) avant de pouvoir être exécuté, et les langages interprétés qui ne nécessitent pas cette traduction préalable (comme Java, Python...).
2Encapsuler : terme informatique indiquant que le langage crée une structure de données et des fonctions, le tout appelé objet, associé à un élément ou à un ensemble quelconque.
3Il vaut mieux utiliser <br/> car le code sera valide dans un fichier xhtml qui est une version plus restrivtive de html dans lequel toute balise ouvrante doit être associée à sa balise fermante ou bien terminée par /> quand elle est oprheline comme <br/> ou <meta .../>.
4le contexte d'une fonction c'est tout le bloc d'instructions à l'intérieur de la fonction, et des fonctions qu'elle appelle, et le contexte du document c'est toutes les instructions.
5 On peut également accéder aux valeurs des champs de l'objet événement par le biais de la variable globale window.event qui mémorise sa valeur courante mais son usage est déconseillé car elle sera peut-être déclaré deprecated dans un certain futur.