Lors des 4 prochaines séances vous allez vous initier au JS afin de permettre des interactions riches entre l'utilisateur et le site. Concrètement après cette séance vous aller apprendre à :
En complément de ce cours pouvez également regarder cette vidéo (regarder la séquence entre 1H10 et 3H00):
Celle-ci fait un point rapide sur les objets dans la dernière version de JS:
<script> function sum(num1){ var sum=0; for (var i=1;i<=num1;i++) sum+=i; return sum; } alert(sum(5)); console.log(sum(6)); </script>
<!-- <script> function sum(num1){ var sum=0; for (var i=1;i<=num1;i++) sum+=i; return sum; } document.getElementById("add").addEventListener('click',function(){ console.log(sum(document.getElementById("limite").value)); }); </script> --> <body> <div> <label>chiffre</label><input id="limite" type="value" name="limite"> <button id="add">Add<button> </div> <script> function sum(num1){ var sum=0; for (var i=1;i<=num1;i++) sum+=i; return sum; } document.getElementById("add").addEventListener('click',function(){ console.log(sum(document.getElementById("limite").value)); }); </script> </body>
Décommentez le bloc en haut et commenter celui du bas. Cela ne marche plus. Pourquoi ?
Voici un exemple d'un code (issu de tutos proposés par goocle) qui affiche des informations météorologiques présentent sur un serveur.
Proche du c et autres c-like (c++, java, ...)
Typage des données non obligatoire :
var message ="hi"; // type mis à string message = 100; // legal mais non recommandé car le type est changé en "numeric" var val; // type "undefined"
Gestion des vraiables globales :
function test (){ var message = "hi"; // variable locale phrase="ok"; } test (); alert(message) // erreur alert (phrase) // okinstruction let.
let x = 1; if (x === 1) { let x = 2; console.log(x); // 2 } console.log(x);//1
for-in / for-of : l'énumération. Pas simple de saisir la différence. On utilise normalement for-of sur un contenu itérable (array, map, set, ...) et le for-in sur les propriétés d'un objet Des explications supplémentaires pour comprendre la différence entre les deux.
var person = ["John", "Doe", 25]; for (let x of person) { console.log(x); } //fonctionne, mais ... for (let x in person) { console.log(x); console.log(person[x]); } //on l'utilise plutôt dans ce cas là : let oldCar = { make: 'Toyota', model: 'Tercel', year: '1996' }; for (let key in oldCar) { console.log(key + "-->" + oldCar[key]); //console.log(`${key} --> ${oldCar[key]}`); // autre manière d'écrire } for (let propName in window){ console.log(propName); } //sur les array il y a every, some, filter, foreach mais dans ce cours, à quelques exceptions, on va privilégier for-in for-of (plus dans l'esprit ES6)
function sayHi(name , message , age , ville){ alert("Hello " + name + " , " + message + " " + age); } sayHi("Nicholas", "how are you?");
function sum(){ return arguments [0]+ arguments [1]; } function howManyArgs (){ alert(arguments.length); } function start(name , message , age , ville){ alert(sum (1 ,2)); alert(sum("hi ","bonjour")); howManyArgs("string" ,45); howManyArgs (); howManyArgs (12); }Les fonctions sont des variables/objets :
function callSomeFunction(someFunction , someArgument){ return someFunction(someArgument); } function add10(num){ return num +10; } var result1=callSomeFunction(add10 ,10); console.log(result1); function getGreeting(name){ return "Hello , " + name; } var result2 = callSomeFunction(getGreeting , "Bob"); console.log(result2);
var num1 =5; var num2=num1; // valeur copiée var obj1=new Object (); var obj2= obj1; obj1.name="Nicholas"; console.log(obj2.name);//"Nicholas"
Le passage d'arguments dans les fonctions est réalisé de la manière suivante : les types primitifs sont passés par valeur et le reste par référence.
function setName(obj , age){ obj.name="Nicholas"; age =35; } var person=new Object (); var _age =21; setName(person , _age), alert(person.name);// Nicholas alert(_age);//21
Mais Attention ! Un peu comme en C++ avec le passage par adresse. Le passage par référence est un abus de langage. C’est en fait un passage par copie de référence. Si la référence est réaffectée sur un autre objet, la référence à l’objet d’orgine est perdue.
function setName(obj){ obj.name="Nicholas"; obj = new Object (); obj.name = "Greg"; } var person= new Object (); setName(person); alert(person.name); // Nicholas
Array (collections indexés). Plus d'informations ici.
var colors= new Array (); var colors= new Array (20); var colors= new Array("red", "blue", "green"); //ou var colors = ["red", "blue", "green"]; //tout plein de méthodes très pratiques colors.push("red", "green");// ajoute à la fin colors.pop(); // enlève et retourne le dernier élément colors.unshift("red","green");// ajoute deux éléments au début du tableau colors.shift ();// enlève et retourne le premier élément var values = [3,1,2,5,4]; values.sort (); // trie le tableau par ordre croissant values.reverse ();// inverse le tableau //utilisation de forEach pour appeler des fonctions sur chaque item const array1 = ['a', 'b', 'c']; array1.forEach(element => console.log(element)); //version E6 var fruits = ["apple", "orange", "cherry"]; fruits.forEach(myFunction); function myFunction(item, index) { console.log(index + ":" + item ); } //on préfèrera (plus "léger") var couleurs = ['rouge', 'vert', 'bleu']; couleurs.forEach(function(couleur) { console.log(couleur); });
Collections avec clé (valeurs ou objet ): Maps, Sets. Plus d'informations ici.
//map : on associe une cléf et une valeur var sayings = new Map(); sayings.set("dog", "woof"); sayings.set("cat", "meow"); sayings.set("elephant", "toot"); sayings.size; // 3 sayings.get("fox"); // undefined sayings.has("bird"); // false sayings.delete("dog"); for (var [key, value] of sayings) { console.log(key + " goes " + value); //Set : comme Array mais on garantit qu'une valeur d'un élément Set ne peut y apparaître qu'une seule fois, il est unique pour cette instance de Set var monEnsemble = new Set(); monEnsemble.add(1); monEnsemble.add("du texte"); monEnsemble.add("du texte"); monEnsemble.size; // 2 for (let item of monEnsemble) console.log(item); monEnsemble.add("toto"); for (let item of monEnsemble) console.log(item); monEnsemble.has(1); // true monEnsemble.delete("toto"); monEnsemble.size; // 2 for (let item of monEnsemble) console.log(item);
Les fonctions fléchées : une syntaxe plus courte. Lisez la documentation de mozilla pour en apprendre plus (notamment les subtilités avec this).
// ES5 const add = function(x, y) { return x + y } console.log(add(5, 4)) // 9 // ES6 const addES6 = (x, y) => { return x + y } console.log(addES6(5, 6)) // 11 const addES6Bis = (x, y) => x + y console.log(addES6Bis(5, 6)) // 11 const prices = [4, 5, 14, 20, 45, 3, 8, 15] var greaterThan10 = prices.filter(function(p) { return p > 10 }) console.log(greaterThan10) // [14, 20, 45, 15] const greaterThan10ES6 = prices.filter(p => p >= 10) console.log(greaterThan10ES6) // [14, 20, 45, 15]
Encore quelques exemples d'un même code dans différentes versions :
function disBonjour(){ alert('Bonjour'); } let disBonjour= function(){ alert('Bonjour'); }; disBonjour(); let disBonjourV2= function bonjour(nom){ if (nom){ alert('Bonjour ' + nom); }else{ alert('inconnu'); } }; disBonjourV2(); disBonjourV2("IUT"); const disBonjourV3 = (nom) => { if (nom){ alert('Bonjour ' + nom); }else{ alert('inconnu'); } } disBonjourV3(); disBonjourV3("IUT"); const disBonjourV3 = (nom) => nom; disBonjourV3("nicholas"); const disBonjourV3 = (nom) => { if (nom){return 'Bonjour'+nom;} else{return 'inconnu';} }; alert(disBonjourV3("nicholas"));
Les fonctions fléchées et le mot clef this.
Vous avez l'habitude avec Java, que this référence l'instance d'un objet. En JS c'est aussi le cas. Ainsi, lorsqu'un nouvel objet est instancié chaque nouvelle fonction/méthode nommée définit son propre this.
let pierre = {name: 'Pierre'}; let mathilde = {name: 'Mathilde'}; function disBonjour(){ alert('Bonjour ' + this.name); } pierre.bonjour = disBonjour; mathilde.bonjour = disBonjour; pierre.bonjour(); mathilde.bonjour();
Dans les fonctions fléchées c'est un peu différent. Le this ne fait pas référence à une instance. Dans l'esprit, les fonctions fléchées sont à utiliser comme des méthodes anonymes. Nous utiliserons toujours les fonctions fléchées de manière anonyme. Elles n'utiliseront pas de constructeur.
on voit dans l'exemple ci-dessous qu'une fonction fléchée n'a pas son propre this. Ici il fait référence à l'objet windows.
var obj = { i: 10, b: () => console.log(this.i, this), c: function() { console.log(this.i, this) } } obj.b(); // undefined Window {...} obj.c(); // 10 Object{i:10}
Les fonctions d’ordre supérieur sont des fonctions qui ont au moins une des propriétés suivantes : elles prennent une ou plusieurs fonctions en entrée, elles renvoient une fonction. (définition Wikipédia).
Cette manière de retourner des fonctions permet très facilement de construire des fonctions plus spécifiques et d’éviter d’avoir à passer trop de paramètres. En effet, la fonction retournée “se souvient” des paramètres passés à la fonction parente.
const l18n = { en: { hello: 'Hello', ciao: 'See you' }, es: { hello: 'Hola', ciao: 'Hasta luego' }, fr: { hello: 'Bonjour', ciao: 'À bientôt' } }; function saySomethingToUser(l18n, locale, sayWhat) { return (userName,last) => `${l18n[locale][sayWhat]} ${userName} ${last}`; } const sayHelloInFrench = saySomethingToUser(l18n, 'fr', 'hello'); const sayCiaoInSpanish = saySomethingToUser(l18n, 'es', 'ciao'); console.log(sayHelloInFrench('Jean','truc'));//bonjour Jean truc console.log(sayCiaoInSpanish('Juan'));//Hasta lueago undefined
L'idée est de créer un code minimaliste qui a plus de chance d'être réutilisé qu'un gros code. En programmation fonctionnelle on peut le faire avec de la composition de fonctions.
// nous avons deux élèves dont nous voulons calculer la moyenne const studentAGrades = [4, 7, 6, 1, 6, 6, 9]; const studentBGrades = [3, 9, 6, 6, 5, 0, 2]; // évidemment, on peut faire une fonction de calcul de moyenne function computeMean(grades) { let sum = 0; grades.forEach(grade => { sum += grade; }); return sum / grades.length; } // mais en PF, nous voulons avoir de petites fonctions réutilisables function sum(numsArr) { let sum = 0; numsArr.forEach(nums => { sum += nums; }); return sum; } // tiens, une fonction d'ordre supérieur function divideBy(divisor) { return dividend => dividend / divisor; } // tous les élèves ont le même nombre de notes const divideByNumberOfExams = divideBy(studentAGrades.length); // usage de manière naïve const sumOfGrades = sum(studentAGrades); console.log(divideByNumberOfExams(sumOfGrades)); // 5.571428571428571 // on peut faire un peu mieux en imbriquant les fonctions console.log(divideByNumberOfExams(sum(studentAGrades))); // 5.571428571428571 // mais peu lisible d'imbriquer tous ces arguments à chaque fois // avec la composition de fonctions, on peut créer une nouvelle ƒ plus spécifique const computeMeanComposed = grades => divideByNumberOfExams(sum(grades)); // la même chose en éliminant l'usage d'une variable pour divideByNumberOfExams const computeMeanComposed2 = grades => divideBy(studentAGrades.length)(sum(grades)); // l'appel est bien plus léger console.log(computeMeanComposed(studentAGrades)); // 5.571428571428571 console.log(computeMeanComposed2(studentBGrades)); // 4.428571428571429 // et fonctionne exactement comme notre fonction computeMean console.log(computeMean(studentBGrades)); // 4.428571428571429
Une (première) manière très "java" de faire de l'objet.
* Titulaires de comptes */ class Titulaire{ /** * Titulaires de comptes bancaires * @param {string} nom Nom du titulaire * @param {string} prenom Prénom du titulaire */ constructor(nom, prenom){ this.nom = nom; this.prenom = prenom; } /** * Afficher l'identité du titulaire */ identite(){ console.log(`Le titulaire s'appelle ${this.prenom} ${this.nom}`); } } /** * Compte bancaire */ class Compte{ /** * Création du compte * @param {Titulaire} titulaire Titulaire du compte * @param {number} montant Montant de départ */ constructor(titulaire, montant = 0){ this.titulaire = titulaire; this.solde = montant; } /** * Créditer le compte * @param {number} montant */ crediter(montant){ this.solde += montant; } /** * Débiter le compte * @param {number} montant */ debiter(montant){ this.solde -= montant; } } /** * Compte épargne */ class CompteEpargne extends Compte{ /** * Création du compte * @param {Titulaire} titulaire Titulaire du compte * @param {number} montant Montant de départ * @param {number} taux Taux d'intérêts * @param {number} rythme Rythme de versement des intérêts (en millisecondes) */ constructor(titulaire, montant = 50, taux = 0.005, rythme = 1000){ // Nous transférons les informations utiles à l'objet Compte super(titulaire, montant); // Nous créons les propriétés spécifiques à un compte épargne this.taux = taux; this.rythme = rythme; } } let titulaire3 = new Titulaire("Penneflamme", "Katy"); let compte1 = new Compte(titulaire3, 25); // Nous créditons le compte de 150 euros compte1.crediter(150); // Nous débitons le compte de 20 euros compte1.debiter(20); let compteEpargne = new CompteEpargne(titulaire3, 15, 0.005, 5000);
Une variante :
function Personne(prenom, nom, age, genre, interets) { this.nom = { prenom, nom }; this.age = age; this.genre = genre; this.interets = interets; this.bio = function() { alert(this.nom.prenom + ' ' + this.nom.nom + ' a ' + this.age + ' ans. Il aime ' + this.interets[0] + ' et ' + this.interets[1] + '.'); }; this.salutation = function() { alert('Bonjour ! Je m\'appelle ' + this.nom.prenom + '.'); }; }; var personne1 = new Personne('Bob', 'Smith', 32, 'homme', ['musique', 'ski']); //pour instancier à partir d'une instance existante. var personne2 = Object.create(personne1);
d'autres variantes :
var maVoiture = new Object(); maVoiture.fabricant = "Ford"; maVoiture.modèle = "Mustang"; maVoiture.année = 1969; var maHonda = {couleur: "rouge", roue: 4, moteur: {cylindres: 4, taille: 2.2}}; var Animal = { type: "Invertébrés", // Valeur par défaut value of properties afficherType : function() { // Une méthode pour afficher le type Animal console.log(this.type); } } // On crée un nouveau type d'animal, animal1 var animal1 = Object.create(Animal); animal1.afficherType(); // affichera Invertébrés // On crée un type d'animal "Poissons" var poisson = Object.create(Animal); poisson.type = "Poisson"; poisson.afficherType(); // affichera Poissons
Complétez le code JS permettant de
function Person(name, date){ this.name=name; this.birthDay= new /*compléter*/; this.friends=new /*compléter*/; this.addfriends=function(friend){ /*compléter*/ }; this.sayNameFriends=function(){ /*compléter*/ }; this.olderthanFriends=function(){ /*compléter*/ }; }; function start(){ var P1= new Person("Nicho", new Date(1949,11,10)); var P2= new Person("Fred", new Date(1979,09,27)); var P3= new Person("Seb", new Date(1980,11,20)); var P4= new Person("Ed", new Date(1979,02,04)); P1.addfriends(P2); P1.addfriends(P3); P1.addfriends(P4); P1.sayNameFriends(); P1.sayNameFriendsMarcheAussi(); //if (P1.olderthanFirends(P1.birthDay)) if (P1.olderthanFriends()) console.log("je suis le plus vieux de la bande"); else console.log("je ne suis pas le plus vieux de la bande"); }
Chaque noeud a une propriété nodeType parmi 12 possibles : ELEMENT_NODE, TEXT_NODE, ATTRIBUTE_NODE, COMMENT_NODE, ...
var node=document.getElementById("element").firstChild; alert(node);//affiche [object HTMLParagraphElement] var node=document.getElementById("monImage"); alert(node);//affiche [object HTMLImageElement] var node=document.getElementById("element").firstChild; alert(node);//affiche [object Text] var node=document.getElementById("monImage").attributes[0]; alert(node);//affiche [object Attr]
<div id="papa"> <p id="premierFils">Premier paragraphe </p> <p id="secondFils">Second paragraphe </p> <p id="dernierFils">Dernier Paragraphe </p> </div> <div id="papaBis"> <p id="premierFilsBis">Premier paragraphe </p> <p id="secondFilsBis">Second paragraphe </p> <p id="dernierFilsBis">Dernier Paragraphe </p> </div> <script> function start(){ var pere=document.getElementById("papa"); var fils1=document.getElementById("premierFils"); var fils2=document.getElementById("secondFils"); var fils3=document.getElementById("dernierFils"); alert(pere.firstChild.firstChild.nodeValue); alert(pere.lastChild.firstChild.nodeValue); alert(pere.childNodes[1].firstChild.nodeValue); alert(fils1.parentNode); alert(fils1.nextSibling.firstChild.nodeValue); alert(fils3.previousSibling.firstChild.nodeValue); var pereBis=document.getElementById("papaBis"); var fils1Bis=document.getElementById("premierFilsBis"); var fils2Bis=document.getElementById("secondFilsBis"); var fils3Bis=document.getElementById("dernierFilsBis"); alert(pereBis.firstChild.firstChild.nodeValue); alert(pereBis.lastChild.firstChild.nodeValue); alert(pereBis.childNodes[1].firstChild.nodeValue); alert(fils1Bis.parentNode); alert(fils1Bis.nextSibling.firstChild.nodeValue); alert(fils3Bis.previousSibling.firstChild.nodeValue); } </script>
function start(){ var nodeElem = document.getElementById("elem1"); var nodeListe = document.getElementById("liste"); nodeListe.appendChild(nodeElem); } <ul id="liste"> <li id="elem1"> 1 </li> < li> 2 </li> < li> 2 </li> < li> 3 </li> </ul>
function start(){ var nodeElem = document.getElementById("elem1"); var nodeListe = document.getElementById("liste"); nodeListe.insertBefore(nodeElem, nodeListe.childNodes[nodeListe.childNodes.length-2]); } <ul id="liste"> <li id="elem1"> 1 </li> < li> 2 </li> < li> 2 </li> < li> 3 </li> </ul>
<ul id="liste"> <li id="elem1"> 1 </li> < li> 2 </li> < li> 2 </li> < li> 3 </li> </ul>
document.getElementById("IdNode"); document.getElementsByTagName("tagName"); document.querySelectorAll("CSS selector");
function start(){ var nodeListe = document.getElementsByTagName("img"); //var nodeListe = document.querySelectorAll("p+p img"); //var nodeListe = document.getElementsByClassName("tableau"); alert(nodeListe.length); alert(nodeListe[0].src); alert(nodeListe.item(1).src); } <p> <img src="http://urlz.fr/TnT"/> </p> <p> <img src="http://urlz.fr/TnU"/> </p> <p> <img src="http://urlz.fr/TnR"/> </p>
node.novalue; node.setAttribute("attributName", "new Value");
var node = document.getElementById("p1"); //le texte est un fils de node node.firstChild.nodeValue="mon super texte"; <p id="p1"> </p> var node = document.getElementById("img1"); node.setAttribute("src","http://urlz.fr/TnT"); alert(node.src); <p> <img id="img1" alt="l'atelier du peintre"/> </p>
var node=document.createElement("div"); var node=document.createTextNode("Hello world");
var node = document.createElement("img"); node.setAttribute("src","http://urlz.fr/TnT"); node.title="l'atelier du peintre"; var p1 = document.getElementById("p1"); p1.appendChild(node); <p id="p1"> </p> var node = document.createTextNode("Hellow World"); var p1 = document.getElementById("p1") ; p1.appendChild(node); <p id="p1"> </p>
La gestion des événements en JS est très bien documentée. Je vous encourage à aller lire les sources suivantes :
Dans le cadre de ce cours nous allons survoler cette partie. En JS nous utiliserons généralement la syntaxe suivante :
element.addEventListener(event, listener);
var formulaire = document.getElementById("coordonnees"); formulaire.addEventListener("submit", envoiForm, false); document.getElementById("clickIt").addEventListener("click", RespondClick); document.getElementById("MouseOnIT").addEventListener("mouseover", RespondMouseOver); document.getElementById("add").addEventListener('click',function(){ console.log(sum(document.getElementById("limite").value)); }); // Fonction pour modifier le contenu de t2 function modifyText(new_text) { var t2 = document.getElementById("t2"); t2.firstChild.nodeValue = new_text; } // Ajout d'un écouteur d'évènement au tableau avec une fonction flèche var el = document.getElementById("outside"); el.addEventListener("click", () => { modifyText("four"); }, false);
Comme défini par Mozilla : "Il est souvent souhaitable de référencer l'élément sur lequel le gestionnaire d’événements a été lancé. Si le gestionnaire est attaché à une fonction à l'aide de addEventListener(), la valeur de this dans le gestionnaire est une référence à l'élément.
var Something = function(element) { // |this| est un nouvel objet créé this.name = 'Something Good'; this.onclick1 = function(event) { console.log(this.name); // undefined (indéfini), car |this| est l'élément this.style.color='red'; //OK }; this.onclick2 = function(event) { console.log(this.name); // 'Something Good', car |this| est lié à un nouvel objet créé }; element.addEventListener('click', this.onclick1, false); element.addEventListener('click', this.onclick2.bind(this), false); // Trick } var s = new Something(document.getElementById("paragraphe"));//on récupère un p
Regardez la vidéo ci-dessous et complétez le code permettant de réaliser les modifications observées. A chaque clic le fond du lien change de couleur. SI on clique sur le second lien la couleur du texte et le texte changent.
Regardez la vidéo ci-dessous et complétez le code permettant de réaliser les modifications observées. Deux champs input permettent de saisir deux entiers. Quand on choisit une opérande le résultat de l'opération d'affiche.
<input type="number" id="nombre1" value=0> <select id="op" name="select" > <option value="0" selected="selected">Choose an operator</option> <option value="1" >+</option> <option value="2">-</option> </select> <input type="number" id="nombre2" value=0> <p id="resultat"> </p> <script type="text/javascript"> document.getElementById("op").addEventListener('change', function() { }); </script>
Regardez la vidéo ci-dessous et écrivez le code permettant de réaliser les modifications observées. Le code HTML contient du texte tout simple. Le code javascript doit créer automatiquement un sommaire à partir des balises issues du code HTML.
Regardez la vidéo ci-dessous et complétez le code permettant de réaliser les fonctionnalités observées. Il est possible d'ajouter une personne à une liste. Vous utiliserez OBLIGATOIREMENT une map (documentation). La clé sera le nom de la personne. La liste est affiché par ordre d'ajout. Il est possible de supprimer un nom dans la liste déroulante en choisissant son identité puis en appuyant sur le bouton à côté. Évidemment toute personne effacée l'est de la liste mais également de la liste déroulante.
<style type="text/css"> span { color: blue; } .italique { font-style: italic; color: blue; }</style> <h1> Liste d'utilisateurs </h1> <ul id="donnees"> </ul> <h1>Ajout d'un individu</h1> <div> <label>Nom</label><input id="nom" type="text" name="nom"> <label>Prénom</label><input id="prenom" type="text" name="prenom"> <button id="add">Add</button> </div> <div style="margin-top: 30px;"> <label>Nom à effacer</label> <select id="nomDel" style="width:100px;"> </select> <button id="del">del</button> </div> <script type="text/javascript"> //https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Map var userMap=new Map(); </script>
Regardez la vidéo ci-dessous et complétez le code permettant de réaliser les fonctionnalités observées. Lorsque la souris est positionnée sur l'un des liens le code doit :
Lorsque la souris n'est plus positionnée sur l'un des liens le code doit :
Récupérez ce code source. Il contient entre autre le HTML/CSS du site web. Il vous restera à écrire le javascript nécessaire. Les scripts php et le fichier Json contiennent respectivement les informations sur le volumes de ventes et les prix des planches. Ouvrez ces deux fichiers et jetez un coup d'oeil rapide. C'est ces deux fichiers auxquels va accéder le client par des requêtes AJAX.
Le pitch est simple : votre client vend des planches de surf, il veut pouvoir consulter la quantité de planches qu'il a vendues (3 catégories de planches) et son bénéfice total engrangé et tout ceci depuis un site web. Il peut demander une mise à jour des ventes d'un modèle précis en le sélectionnant dans la liste déroulante. Il peut aussi demander à les mettre à jours tous les modèles en même temps.
Vous décidez donc de lui installer un serveur (web). Dans l'idéal, vous installerez une ubuntu et un serveur apache sur un vps OVH ou openStack de l'université. Voici toutes les informations pour installer votre serveur apache, configurer votre VHost et installer un certificat
SI je ne peux pas vous en fournir un, vous pouvez installer un en local sur votre machine. Par exemple mamp pour mac ou windows. Ou alors simplement en lancer un en ligne de commande comme décrit sur cette documentation. Sur linux rien de compliqué, vous pouvez lancer en ligne de commande ou installer apache.Un script php aura simplement pour rôle de donner les volumes de vente. Le client consultera ses volumes de vente depuis une page web qui ira chercher les informations sur le serveur (script php) à chaque fois qu'il appuiera sur le bouton "actualiser". Les prix de vente et les prix d'achat de chaque modèle de planche sont stockés en dur dans un fichier JSON (prixDeVente.json). Au chargement de la page, il faut aller chercher les informations dans ce fichier et mettre à jour les cases.
La vidéo ci-dessous illustre le résultat final attendu. Dans cette vidéo j'ai mis un point d'arrêt sur le "onload()" pour vous montrer qu'initialement le tableau contient uniquement des valeurs nulles. C'est un script qui va chercher les informations et remplit ensuite le tableau.
L’AJAX est un terme inventé en février 2005 par Jesse James Garret pour désigner l’usage commun de différentes technologies. Pour savoir lesquelles sont utilisées, il suffit d’étudier ce que veut dire ce terme : AJAX = « Asynchronous Javascript And XML ». Visiblement, il y a du Javascript. Sa présence ici indique que les sites Ajax mettront en place un certain niveau d’interaction avec l’utilisateur. Au tour du XML maintenant : ce formalisme, qui permet de représenter n’importe quel ensemble structuré de données, désigne ici l’utilisation du DOM (il s’agit de la représentation de la page sous la forme d’un arbre XML) pour modifier le contenu des pages mais pas seulement. Le XML est aussi employé pour coder les transferts entre le navigateur Web et le serveur du site visité. Il ne nous reste plus qu’à traiter le cas du « Asynchronous »... En fait, c’est à ce terme qu’Ajax doit son succès. Il désigne une technologie mise au point par Microsoft en 1999 et qui longtemps n'a pas particulièrement été célèbre. Celle-ci permet à un programme Javascript d’envoyer une requète au serveur puis de continuer son exécution sans attendre que la réponse n’arrive. Une fois cette réponse effectivement arrivée, une fonction est appelée et le résultat peut être utilisé dans la page. Et c’est tout ? En fait, oui, Ajax ce n’est rien que ça. Mais ceci dit, si elles sont utilisées judicieusement, les requêtes asynchrones peuvent à la fois alléger la charge des serveurs et permettre la création de sites plus interactifs. Ce qui est pratique, c’est qu’il est possible (grâce à DOM), de ne modifier qu’une partie de la page afin de faire apparaitre le résultat d’une requête. Etant donné que le serveur n’aura pas eu besoin de renvoyer toute la page, celui-ci aura une charge moindre et puisque l’on peut se passer de ce re-chargement complet de la page, le site Web devient plus interactif. L’AJAX fait donc partie des technologies permettant d’améliorer significativement les IHM du WEB.
Pour envoyer une requête au serveur il vous faut créer un objet. Le côté délicat de la chose est que, selon le navigateur, il faut créer des objets différents. Le constructeur XMLHttpRequest permet de créer un objet requête pour Mozilla, Firefox et Safari. ActiveXObject permet de créer un objet requête pour IE.
var xmlhttp = new XMLHttpRequest();
C'est la fonction Prix de vente qui va gérer l'appel au serveur et récupérer les informations présentes dans le fichier JSON. Dans la fonction prixDeVente() créez un objet XMLHttpRequest. Mettez un point d'arrêt dans le navigateur et vérifier que l'objet a bel et bien été créé (regardez également les attributs et méthodes de cet objet)
Il est maintenant nécessaire de configurer cet objet afin que l'envoi de la requête AJAX puisse se faire. Il y a deux lignes de code à écrire avant d'envoyer effectivement la requête.
xmlhttp.open("GET",URL,true) ;Le premier argument indique si l’envoi de la requête est en mode GET ou POST, le deuxième contient l’url du script php (ou fichier JSON) qui s’exécute sur le serveur WEB, le dernier argument indique si la requête doit être asynchrone ou non.
xmlhttp.onreadystatechange = function () { //code exécuté lors du retour de la fonction. alert("retour de la requête ! ") }
xmlhttp.send(null);
Si tout marche bien, vous devriez avoir la popup qui s'ouvre plusieurs fois. Pour le moment, vous avez considéré que le serveur fait une et une seule réponse à votre requête... mais ce n’est pas le cas ! Pour comprendre il faut s’intéresser aux codes d’état. L’objet requête possède un attribut (readyState) qui informe le navigateur de l’état dans lequel se trouve la requête. C’est à chaque changement de ce code d’état que la fonction actualiserPage() est appelée. La figure suivante résume les 4 états possibles de la requête.
Complétez le code suivant afin que la popup ne s'ouvre qu'au moment où la requête contient la réponse attendue.
if (xmlhttp.??? == ??? && xmlhttp.status == 200)
Regardez sur cette documentation pour comprendre l'intéret de vérifier que le statut de la requête est à 200.
La réponse du serveur (les données JSON) a été mise dans l'attibut "responseText" de l'objet xmlhttp.
planches = JSON.parse(xmlhttp.responseText); planches.forEach(function(planches) { //sélection du noeud du node à modifier document.querySelectorAll(?)[0].?.?=planches.?; document.querySelectorAll(?)[0].?.?=planches.?; })
Le site web contient un menu déroulant permettant de choisir la planche dont on souhaite récupérer les volumes vendus. Regardez la documentation suivante expliquant comment envoyer des paramètres au script php (en GET ou en POST).
Complétez la fonction getPlanchesVendues() afin qu'elle envoie une requête asynchrone au script php avec comme paramètre le nom de la planche choisie par l'utilisateur. Attention observez que dans cette version du code, le code de la fonction callBack n'est pas écrit directement dans getPlanchesVendues(). Quand le code devient un peu trop gros on préfèrera le délocaliser.
var reqPlanches = new XMLHttpRequest(); function getPlanchesVendues() { #on récupère la valeur choisie dans la liste déroulante var planche=?; #on paramètre la requête asynchrone reqPlanches.open(?); #définition de la fonction callBack reqPlanches.? = updatePlanchesVendues #envoi de la requête reqPlanches.send(null);// ou reqPlanches.send(?) }
Complétez le code de la fonction updatePlanchesVendues() permettant de gérer la réponse du serveur au format XML. Cette réponse se trouve dans le champ responseXML de l'objet.
var reponse = reqPlanches.responseXML;
La réponse est en XML. XML se présente sous la forme d'un arbre comme pour le DOM. Par exemple pour récupérer le nombre de planches lacanau il faut écrire :
reponse.getElementsByTagName("lacanau")[0].firstChild.nodeValue
Terminez le contenu de la fonction updatePlanchesVendues() afin de mettre à jour la première colonne du tableau puis le gain total réalisé par le vendeur de planche.
La volonté de créer du code asynchrone avec la technique vue juste avant peut devenir compliqué (peu lisible et peu modulaire, difficulté de gérer les erreurs) si on a besoin de gérer un grand nombre de requêtes asynchrones. Une possibilité est d'utiliser les promesses pour simuler un code séquentiel.
Une promesse est une fonction à laquelle on passera deux arguments : resolve et reject. Cette fonction est exécutée immédiatement par l'implémentation de Promise qui fournit les fonctions resolve et reject (elle est exécutée avant que le constructeur Promise ait renvoyé l'objet créé). Les fonctions resolve et reject, lorsqu'elles sont appelées, permettent respectivement de tenir ou de rompre la promesse. On attend de l'exécuteur qu'il démarre un travail asynchrone puis, une fois le travail terminé, appelle la fonction resolve (si tout s'est bien passé) ou la fonction reject (lorsqu'il y a eu un problème) pour définir l'état final de la promesse. Si une erreur est générée par l'exécuteur, la promesse est rompue et la valeur de retour de l'exécuteur est ignorée. (définition mozilla)
const maPremierePromesse = new Promise((resolve, reject) => { // réaliser une tâche asynchrone et appeler : // resolve(uneValeur); // si la promesse est tenue // ou // reject("raison d'echec"); // si elle est rompue }); //L'objet promise contient deux méthodes : //.then(f) appelée quand le résultat est disponible //.catch(f) appelée en cas d'erreur
Exemple d'une promesse.
const promise1 = new Promise(function(resolve, reject) { //L'asynchronisme est simulé par le timeout aléatoire. setTimeout(function() { resolve('foo'); }, Math.random() * 2000 + 1000); }); //Quand la promesse est tenue c'est ce code (ce qui suit then) qui sera exécuté. resolve est "lié" à then. promise1.then(function(value) { console.log(value); // expected output: "foo" }); //exécution de la promesse console.log(promise1); // expected output: [object Promise] foo
Autre exemple d'une promesse.
function methode1(){ return new Promise((resolve, reject) => { //peut être un chargement d'une grosse image, d'un script, une requette XHR console.log("Methode 1 fait un truc asynchrone"); // réussir une fois sur deux if (Math.random() > .1) { resolve("Tout va bien dans la méthode asynchrone 1"); } else { reject(Error('Problème méthode 1')); } }) } function methode2(reponse){ console.log(reponse); return new Promise((resolve) => { //peut être un chargement d'une grosse image, d'un script, une requette XHR console.log("Methode 2 fait un truc asynchrone"); // 100% de réussite resolve("Tout va bien dans la méthode asynchrone 2"); }) } methode1() .then((reponse) => methode2(reponse)) .then((reponse)=> console.log(reponse,"!")) .catch((alert)=>console.log(alert)) .then(() => console.log ("tout est terminé"));
Depuis ECMAScript 2017, les opérateurs async/await permettent de simplifier l'écriture des promesses. async renvoie systématiquement une promesse. Si une erreur est levée pendant l’exécution de la fonction, la promesse est rejetée, et si une valeur est retournée, la promesse est résolue avec cette valeur.
async function faireQqc(){ return 'resultat'; } //équivaut à return Promise.resolve('résultat'); async function toto() { try { //await attend le résultat d'une promesse const result = await faireQqc(); const newResult = await faireQqcAutre(result); const finalResult = await faireUnTroisiemeTruc(newResult); console.log('Résultat final : ' + finalResult); } catch(error) { //la gestion des erreurs est un peu simplifiée failureCallback(error); } }
Nous allons étudier le très bon exemple proposé par https://github.com/mdn/js-examples/blob/master/promises-test/index.html
function imgLoad(url) { // Create new promise with the Promise() constructor; // This has as its argument a function // with two parameters, resolve and reject return new Promise(function(resolve, reject) { // Standard XHR to load an image var request = new XMLHttpRequest(); request.open('GET', url); request.responseType = 'blob'; // When the request loads, check whether it was successful //observez ici qu'on n'utilise pas onreadystatechange //onload: pour réagir à une requête terminée avec succès. //onerror: pour réagir à une requête terminée avec erreur. //onabort: pour réagir à une requête annulée par la méthode abort() //ontimeout: pour réagir à une requête en timeout //il y en a d'autres //Ces évènements sont exclusifs, un seul sera appelé lors de chaque requête. request.onload = function() { if (request.status === 200) { // If successful, resolve the promise by passing back the request response resolve(request.response); } else { // If it fails, reject the promise with a error message reject(Error('Image didn\'t load successfully; error code:' + request.statusText)); } }; request.onerror = function() { // Also deal with the case when the entire request fails to begin with // This is probably a network error, so reject the promise with an appropriate message reject(Error('There was a network error.')); }; // Send the request request.send(); }); } // Get a reference to the body element, and create a new image object var body = document.querySelector('body'); var myImage = new Image(); // Call the function with the URL we want to load, but then chain the // promise then() method on to the end of it. This contains two callbacks imgLoad('myLittleVader.jpg').then(function(response) { // The first runs when the promise resolves, with the request.response // specified within the resolve() method. var imageURL = window.URL.createObjectURL(response); myImage.src = imageURL; body.appendChild(myImage); // The second runs when the promise // is rejected, and logs the Error specified with the reject() method. }, function(Error) { console.log(Error); });
Écrivez le code source permettant de vérifier si un login est disponible ou pas. Lorsque l'utilisateur appuie sur TAB une promesse est en charge de lancer une requête XHR afin que le serveur vérifie si le login est disponible. En fonction de la réponse le texte adéquat apparaît. Il faut gérer cela avec une promesse. C'est le script login.php qui vous indique si le login est disponible ou pas. Attention ceci n'est en aucun cas un système de sécurité. Toutes les vérifications se font côté serveur. Ce genre de vérifications sont uniquement utiles pour l’expérience utilisateur.
Vous pouvez regarder cette vidéo proposée par Microsoft. C'est un bon complément à ce cours.
La majorité des exemples et exercices qui suivent sont issus des notes de cours de Stéphane Fossé sur le JQuery.
jQuery est un framework Javascript sous licence libre qui permet de faciliter des fonctionnalités communes de Javascript. L'utilisation de cette bibliothèque permet de gagner du temps de développement lors de l'interaction sur le code HTML d'une page web, l'AJAX ou la gestion des évènements. Pour pouvoir utiliser la bibliothèque jQuery, vous pouvez la télécharger sur le site jQuery.com, puis la charger dans votre page.
Tout le fonctionnement de la bibliothèque se base sur la fonction jQuery(). Cette fonction est généralement écrite de manière abrégée $(). Par exemple, pour accéder à tous les éléments p de notre page HTML, nous écrirons : $('p') ou pour sélectionner tous les éléments portant la class para, en $('.para').
Le jQuery simplifie la gestion d'évènements en mettant à notre disposition différentes méthodes prêtes à l’emploi. Ces méthodes vont tout simplement généralement porter le nom des évènements auxquels on souhaite réagir, comme par exemple la méthode click() qui va nous permettre de gérer l’évènement click. Il existe également des interactions avec le clavier. Les méthodes keydown(), keyup() et keypress() vont nous permettre de répondre à des évènements relatifs au clavier de l’utilisateur.
Découverte de jQuery
paragraphe 1
paragraphe 2
Avec JQuery la gestion des formulaires est simplifiée. Dans l'exemple ci-dessous vous pouvez voir comment il est possible de récupérer la valeur sélectionnée. Vous pouvez également observer comment intercepté l'envoie du formulaire.
Découverte de jQuery
Depuis la version 1.7 de jQuery, la documentation officielle recommande d’utiliser on() plutôt que les notations raccourcies précédentes.
La méthode on() va prendre en arguments le nom d’un évènement et une fonction à exécuter en cas de déclenchement de l’évènement passé en premier argument.
L'intérêt de la méthode on() est qu'elle sera effective même sur un élément qui n'a pas été créé au chargement du DOM.
La méthode jQuery off() va nous permettre de supprimer des gestionnaires d’évènements attachés avec la méthode on().
Regardez l'aide de .toggleClass(className) et de .slideToggle()
pour bien comprendre l'exemple.
Pour forcer le déclenchement d’un évènement. : trigger() et triggerHandler()
Ces deux méthodes vont prendre en argument le nom d’un évènement. On peut également leur passer d'autres arguments optionnels.
trigger() va déclencher l’évènement spécifié en argument ainsi que le comportement par défaut de l’évènement tandis que triggerHandler() ne va pas déclencher le comportement par défaut de l’évènement.
trigger() va être exécutée pour tous les éléments possédant les caractéristiques ciblées tandis que triggerHandler() ne va s'exécuter que pour le premier élément ciblé rencontré.
Découverte de jQuery
This is a paragraph.
Click any p element to make it disappear. Including this one.
La manipulation du DOM est rendue simple car des méthodes jQuery ont été créées pour servir à la fois de getter ou de setter selon le type d'arguments qu’on va leur passer.
Commencez par regarder l'API des méthodes permettant d'accéder aux ascendants et testez le code ci-dessous.
- I
- II
- A
- B
- 1
- 2
- 3
- C
- III
Lisez rapidement la suite. Je liste les méthodes permettant de manipuler facilement les nœuds du DOM. Si vous avez compris comment le faire en JS, alors le faire en JQuery ne posera pas de problème.
Sur le même principe voici les méthodes à connaitre pour parcourir les nœuds enfants.
Il existe également de méthode pour manipuler le texte.
Type 'correct' to validate.
Ou supprimer du contenu
JQuery permet également de simplifier l'utilisation de requêtes AJAX. Jquery propose plusieurs variantes bien résumées sur le site de W3 school. Ici nous verrons uniquement la méthode .ajax()
Pour bien comprendre comment l'utiliser, il faut comprendre le schéma suivant expliquant les différentes étapes suivantes :
Généralement on utilise success() si on a simplement besoin d'avoir accès aux données et complete() si on souhaite avoir accès à l'objet XMLHttpRequest.
Étudiez et complétez ce code source permettant de faire une requête AJAX à un script qui retourne une valeur en fonction du paramètre qui lui est passé (regardez le vidéo pour comprendre le comportement souhaité).
Le code php
$id=$_GET["band"]; if ($id=="pearl"){ $arr = array ("name"=>"PearlJam","city"=>"Seattle"); echo json_encode($arr); } else if ($id=="acdc"){ $arr = array ("name"=>"AC-DC","city"=>"Sydney"); echo json_encode($arr); }
Le code JS à compléter
Exercice .ajax()