Iut info BDX

Initiation au JavaScript pour la réalisation de sites


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:

×

Présentation express du JS


Du JS pour quoi faire ?

Du code classique : stocker des valeurs, opérations, itérations, retours, fonctions, objets…

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

Permettre des interactions utilisateurs et des modifications du HTML

      
<!--  <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 ?

Echanger des informations avec un serveur

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.

Bases de la programmation JS

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) // ok
instruction 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)   

Quelques spécificités sur les fonctions :

 
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  

Les collections

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

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.

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

La composition de fonctions

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

Les objets et le JS

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


Exercice 1 : fonctions, parcours de collections; ...

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");

    }   

JS et le DOM


Les concepts les plus importants

Comprendre le DOM et sa hiérarchie de nœuds

Les noeuds sont typés

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]

La relation entre les nœuds




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


Les méthodes de manipulation du DOM à connaître

Ajout d’un noeud (déplacement si le nœud existe déjà)

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>

Insertion d’un noeud avec insertBefore (déplacement si le noeud existe déjà)

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>


Suppression d'un noeud avec removeChild

     <ul id="liste">
    <li id="elem1"> 1 </li>
    < li> 2 </li>
    < li> 2 </li>
    < li> 3 </li>
  </ul>

Accéder à des noeuds du DOM

  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>

Modifier un noeud du DOM

  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>

Créer un noeud du DOM

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>


Interactions en JS : HTML/DOM event

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

Référence à this

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

Exercice 2

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.

Exercice 3

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>
 

Exercice 4

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.

Exercice 5

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> 
 

Exercice 6

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 :

Requête AJAX et données structurées


Préambule

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.

Principe d'une requète asynchrone

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();
            

Question 1

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)

Question 2

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.

Question 3

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.

Question 4

La réponse du serveur (les données JSON) a été mise dans l'attibut "responseText" de l'objet xmlhttp.

Envoi d'une requête avec des paramètres et traitement de données XML

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

Question 5

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(?)
          }
        

Question 6

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.

Les promesses

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);
  }
}

Promesses et Ajax

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);
  });
 

Exercice

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

JQuery


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.

        
       <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
     
      

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

    
    <script>
    $( document ).ready(function() {
        console.log( "document loaded" );
        $('.para').hide(); 
    });
    </script>
  
  

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.

    
      <script>
      $(document).ready(function() {
         $('p').click(function() {
            $(this).hide();
         });
         /*Observez la différence entre this et p*/
         $("p").mouseenter(function() {
            $("p").css("background-color", "yellow");
         });

         $("p").mouseleave(function() {
            $(this).css("background-color", "red");
         });

         /*Il est possible de remplacer les deux dernières lignes par */
         
         /*$("p").hover(function() {
            $(this).css("background-color", "yellow");
         }, function() {
            $(this).css("background-color", "pink");
         });*/
      });
   </script>
</head>

<body>
   <h1>Découverte de jQuery</h1>
   <p class='para'>paragraphe 1</p>
   <p>paragraphe 2</p>
</body>

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.

    
      <script>
      $(document).ready(function() {
         $("select").change(function() {
            var str = "";
            $("select option:selected").each(function() {
               str += $(this).text() + " ";
            });
            $("div").text(str);
         })
         $('form').submit(function() {
            alert('Formulaire envoyé');
         });
      });
   </script>
   <style>
      .focused {
         background-color: #00f0ff;
      }
   </style>
</head>

<body>
   <h1>Découverte de jQuery</h1>
   <form method="GET" action="#">
      <select name="bonbon" multiple="multiple">
         <option>Chocolat</option>
         <option selected="selected">Candy</option>
         <option>Taffy</option>
         <option selected="selected">Caramel</option>
         <option>Cookie</option>
      </select>
      <input type="submit" value="Envoyer">
   </form>
   <div></div>
</body>
    

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

    
      <script>
      $(document).ready(function() {
         $("p").on("mouseover mouseout", function() {
            $(this).toggleClass("intro");
         });
         // $("div").on("click", "p", function() {
         //    $(this).slideToggle();
         //    $("<p>This is a new paragraph.</p>").insertAfter($(this));
         // });
         
         $("#add").on("click",function(){
            $("<p>This is a new paragraph.</p>").insertAfter($(this));
         })
         $("div").on("click", "p",toggleSlide);
         
         function toggleSlide(){
          $(this).slideToggle();
         }
         $("#stop").on("click", function(){
            $("div").off("click",toggleSlide);
         });
         /*Il faut maintenant éviter de faire :
         $("button").click(function() {
            $("<p>This is a new paragraph.</p>").insertAfter("button");
         });*/
         $("#old").click(function() {
            $("input").trigger("focus");
         });
         $("#new").click(function() {
            $("input").triggerHandler("focus");
         });
         $("input").focus(function() {
            $("<span>Focused!</span>").insertAfter($(this));
         });
      });
   </script>
   <style>
      .intro {
         background-color: #00f0ff;
      }
   </style>
</head>

<body>
   <h1>Découverte de jQuery</h1>
   <div style="background-color:yellow">
      <p>This is a paragraph.</p>
      <p>Click any p element to make it disappear. Including this one.</p>
      <button id="add">Insert a new p element after this button</button>
   </div>
      <button id="stop">test Off()</button>

   <button id="old">.trigger("focus")</button>
   <button id="new">.triggerHandler("focus")
   </button>
   <br/>
   <br/>
   <input type="text" value="To Be Focused 1" />
   <br>
   <input type="text" value="To Be Focused 2" />
</body>
    

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.

          
<body>
 
<ul class="level-1 yes">
  <li class="item-i">I</li>
  <li class="item-ii">II
    <ul class="level-2 yes">
      <li class="item-a">A</li>
      <li class="item-b">B
        <ul class="level-3">
          <li class="item-1">1</li>
          <li class="item-2">2</li>
          <li class="item-3">3</li>
        </ul>
      </li>
      <li class="item-c">C</li>
    </ul>
  </li>
  <li class="item-iii">III</li>
</ul>
<div id="result"></div> 
<script>
$("li.item-a").parent().css("background-color","red");

var parentEls = $( "li.item-a" ).parents()
  .map(function() { //crée un nouveau tableau avec les résultats de l'appel d'une fonction 
    return this.tagName;
  })
  .get()  //renvoie un élément d'un objet Map.
  .join( ", " );// crée et renvoie une nouvelle chaîne de caractères en concaténant tous les éléments d'un tableau 
  
$("#result").append( "<strong>" + parentEls + "</strong>" );

//match avec tous les parents de li.item-2 de classe "yes" jusqu'à trouver ul.level-1 (exclu)
$("li.item-2").parentsUntil( $("ul.level-1"),".yes").css( "border", "3px solid green" );

$("li.item-2").closest($("ul"),".yes").css( "border", "3px solid yellow" );



</script>
 
</body>
    
  

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.

    
      <style>
      p {
         margin: 0;
         color: blue;
      }
      
      div,
      p {
         margin-left: 10px;
      }
      
      span {
         color: red;
      }
   </style>
   <script src="https://code.jquery.com/jquery-1.10.2.js"></script>
</head>

<body>

   <p>Type 'correct' to validate.</p>
   <form action="javascript:alert( 'success!' );">
      <div>
         <input type="text">
         <input type="submit">
      </div>
   </form>
   <span></span>

   <script>
      $("form").submit(function(event) {
         if ($("input:first").val() === "correct") {
            $("span").text("Validated...").show();
         }
         else{
            $("span").text("Not valid!").show().fadeOut(1000);
         }
         event.preventDefault();
      });
   </script>

</body>

    

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

        
          <html>

          <head>
            <meta charset="utf-8">
            <script src="https://code.jquery.com/jquery-1.10.2.js"></script>
          </head>

          <body>
           <h1>Exercice .ajax() </h1>
           <select id="band">
            <option value="">--Please choose an option--</option>
            <option value="pearl">Pearl Jam</option>
            <option value="acdc">AC/DC</option>
          </select>
          <button id="envoyer">Get Result</button>
          <script>
            $("#envoyer").click(function() {
             $.ajax({
              url: 'rock.php',
            type: 'get', // Le type de la requête HTTP, ici devenu POST
            data: 'band=' + $("#band").val(), // On fait passer notre vairable au script
            dataType: 'json', //type de données à recevoir
            success: function(data, status) { 
             //A COMPLETER
           },
           error: function(data, status, error) {
                 //A COMPLETER
               }
             });
           });
         </script>
       </body>

       </html>
     
   

Réalisez quelques exercices permettant de vous faire un peu la main sur les fonctionalités de base de JQuery

        
          <body>
<div id="header">
      <div id="menu">
        <ul>
          <li><a href="#">Accueil</a></li>
          <li><a href="#">Contenu</a></li>
          <li><a href="#">Matériel</a></li>
          <li><a href="#">Contact</a></li>
        </ul>
      </div>
      <div id="logo">
        <h1 class="title">le joli titre</h1>
        <h2>Programmation web</h2>
      </div>
    </div>

    <div id="page">
      <div id="sidebar">
        <ul>
          <li>
            <h2>Du texte...</h2>
            <p>Un paragraphe.</p>
          </li>
          <li>
            <h2>Encore du texte...</h2>
            <p>Toujours plus de texte. </p>
            <p>Toujours plus de texte./p>
          </li>
        </ul>
      </div>

      <div id="content">
        <div class="post">
          <p class="meta">
            <span class="date">aujourd'hui</span>
            Publié par <a href="#">Jack</a>
          </p>
          <h2 class="title">Exemple de post</h2>
          <div class="entry">
            <p>Il est toujours difficile de trouver du texte pertinent à
              écrire dans un exemple de page web.Ainsi est né Lorem Ipsum.</p>
            <p>
              On ajoute aussi un exemple de lien: <a href="http://www.lipsum.com/">Lorem ipsum</a>
            </p>
          </div>
          <div>
            <a href="#" class="links">Lire l'article au complet</a>
          </div>
        </div>
        <div class="post">
          <p class="meta">
            <span class="date">Mardi, le 6 septembre 2011 01:00</span>
            Publié par <a href="#">Jack</a>
          </p>
          <h2 class="title">Exemple de post</h2>
          <div class="entry">
            <p>Il est toujours difficile de trouver du texte pertinent à
              écrire dans un exemple de page web. Ainsi est né Lorem Ipsum.</p>
            <p>
              On ajoute aussi un exemple de lien : <a href="http://www.lipsum.com/">Lorem ipsum</a>
            </p>
          </div>
          <div>
            <a href="#" class="links">Lire l'article au complet</a>
          </div>
        </div>

        <div class="post">
          <h2 class="title">Formulaire d'inscription</h2>
          <div class="entry">
            <form method="post" action="javascript:controlAndSendForm ();">
              <table>
                <tr>
                  <td><label for="email">Email :</label></td>
                  <td>
                    <input type="text" name="email" id="email" maxlength="25" />
                  </td>
                </tr>
                <tr>
                  <td><label for="password">Mot de passe :</label></td>
                  <td>
                    <input type="password" name="password" id="password" maxlength="10" />
                  </td>
                </tr>
                <tr>
                  <td><label>Sexe :</label></td>
                  <td>
                    <input type="radio" name="sexe" value="H" /> Homme
                    <br/>
                    <input type="radio" name="sexe" value="F" /> Femme
                  </td>
                </tr>
                <tr>
                  <td><label>Date de naissance :</label></td>
                  <td>
                    <select name="d_jour">
                      <option>1</option>
                      <option>2</option>
                      <option>...</option>
                      <option>31</option>
                    </select>
                    &nbsp;
                    <select name="d_mois">
                      <option>1</option>
                      <option>2</option>
                      <option>...</option>
                      <option>12</option>
                    </select>
                    &nbsp;
                    <select name="d_annee">
                      <option>1940</option>
                      <option>1941</option>
                      <option>...</option>
                      <option selected="selected">2009</option>
                    </select>
                  </td>
                </tr>
                <tr>
                  <td colspan="2">
                    <label for="desc">Entrez une courte description sur vous :</label>
                  </td>
                </tr>
                <tr>
                  <td colspan="2">
                    <textarea name="desc" id="desc" cols="50" rows="5"></textarea>
                  </td>
                </tr>
                <tr>
                  <td><label>Vos centres d'intérêts :</label></td>
                  <td>
                    <input type="checkbox" name="interets[]" value="informatique" />
                    &nbsp; Informatique
                    <br/>
                    <input type="checkbox" name="interets[]" value="musique" />
                    &nbsp; Musique
                    <br/>
                    <input type="checkbox" name="interets[]" value="cinema" />
                    &nbsp; Cinéma
                  </td>
                </tr>
                <tr>
                  <td><label for="hobbies">Vos Hobbies :</label></td>
                  <td>
                    <select name="hobbies[]" id="hobbies" multiple="multiple">
                      <option value="sport">J'aime le sport</option>
                      <option value="cinema">J'aime le cinéma</option>
                      <option value="musique">J'aime la musique</option>
                      <option value="culture">J'aime la culture</option>
                    </select>
                  </td>
                </tr>
                <tr>
                  <td colspan="2">
                    <input type="submit" value="M'inscrire" />
                  </td>
                </tr>
              </table>
            </form>
          </div>
        </div>

        <div id="footer">
          <p>
            Copyright &copy; 
          </p>
        </div>
      </div>
    </div>
       <script src="https://code.jquery.com/jquery-1.10.2.js"></script>
    <script src="script.js"></script>
  </body>
        
      
        
          body {
   margin: 0px;
   background: #101010;
   font-family: Arial, Helvetica, sans-serif;
   font-size: 12px;
   color: #72809F;
}

h1,
h2 {
   margin: 0px;
   font-weight: normal;
}

h1 {
   font-size: 44px;
}

h2 {
   font-size: 18px;
}

p,
ul,
ol {
   margin-top: 0px;
   line-height: 240%;
   text-align: justify;
}

a {
   color: #ADBFE1;
}

a:hover {
   text-decoration: none;
}

#header {
   width: 960px;
   height: 160px;
   margin: auto;
}

#menu {
   float: left;
   width: 900px;
   height: 44px;
}

#menu ul {
   padding: 0px;
   list-style: none;
   line-height: normal;
}

#menu li {
   display: block;
   float: left;
   background: #182739;
}

#menu a {
   float: left;
   height: 34px;
   padding: 30px 30px 0px 30px;
   text-align: center;
   font-weight: normal;
   color: #FFFFFF;
}

#menu .red {
   background: red;
}

#page {
   width: 960px;
   margin: 0px auto 20px auto;
}

#logo {
   width: 960px;
   height: 100px;
   margin: auto;
   font-family: Georgia, "Times New Roman", Times, serif;
   color: #FFFFFF;
}

#logo h1 {
   padding-top: 80px;
   font-size: 52px;
   font-style: italic;
   text-transform: lowercase;
}

#content {
   float: right;
   width: 640px;
   padding: 40px 30px 0px 20px;
}

.post {
   margin-bottom: 45px;
   padding-bottom: 30px;
   border-bottom: #203253 dashed 1px;
}

.post .title {
   padding-bottom: 10px;
   text-transform: uppercase;
   font-weight: bold;
   font-size: 28px;
   color: #FFFFFF;
   border-bottom: 4px dashed #203253;
}

.post .date {
   color: #FFFFFF;
}

.post .meta {
   margin-left: 2px;
   padding: 4px 30px 0px 0px;
   font-weight: normal;
   font-size: 10px;
   font-style: italic;
}

.post .meta a {
   text-decoration: none;
   color: #FFFFFF;
}

.post .entry {
   padding: 10px 0px 20px 0px;
}

.post .links {
   height: 18px;
   margin: 0px 0px 4px 0px;
   padding: 6px 10px 5px 10px;
   background: #182739;
   font-size: 11px;
   text-decoration: none;
   font-weight: bold;
   color: #FFFFFF;
}

#sidebar {
   float: left;
   width: 250px;
   padding-top: 40px;
}

#sidebar ul {
   padding: 0px;
   list-style: none;
}

#sidebar p {
   padding-top: 10px;
   line-height: 200%;
}

#sidebar h2 {
   height: 37px;
   padding: 15px 0px 0px 20px;
   text-transform: uppercase;
   font-size: 18px;
   font-weight: bold;
   color: #FFFFFF;
}

#sidebar h2.orange {
   color: orange;
}

#footer {
   height: 110px;
}

#footer p {
   text-align: center;
   font-size: 12px;
   line-height: normal;
}

.error {
   border: 3px solid red;
}
        
      

Récupérez, l'archive suivante. Elle contient la base du code source (html/css) permettant de réaliser un site web qui affiche une citation de JCVD quand on clique sur le picto. Quand il n'y a plus de citation un message s'affiche. Regardez la vidéo et écrivez le javaScript permettant de reproduise le même comportement.

Service Worker


Mettre en œuvre des sites web offrant une expérience utilisateur riche et fonctionnant aussi bien sur tablette, smartphone et site web est devenu maintenant un standard. On parle de "progressive web applications". Désormais, un site web doit offrir des fonctionnalités telles que : l'envoi de notifications push, l'utilisation du site même sans connexion internet, assurer la synchronisation du contenu sans avoir à rafraichir le site (Définition et exercices largement inspirés du très bon tutoriel de Google ).

Un service worker est un script que votre navigateur exécute en arrière-plan, indépendamment d'une page Web, sans nécessité d'une interaction utilisateur. Concrètement, un service worker joue le rôle d'un proxy vous permettant de contrôler la façon dont les requêtes réseau de votre page sont traitées. Il peut se stopper s'il n'est plus utilisé et redémarrer lorsque nécessaire. Le cycle de vie d'un service worker est totalement distinct de celui d'une page web.

Les services workers sont basés sur le concept de "promesses". Lisez ce tutoriel si vous souhaitez creuser un peu le sujet.

Pour installer un service worker vous devez l'enregistrer. Ceci se fait en JavaScript. Cet enregistrement fait que le navigateur lance l'étape d'installation, en arrière-plan, du service worker. Une fois activé, le service worker contrôlera les pages qui se sont abonnées à lui. Un service worker est soit dans un état "à l'arrêt" pour économiser de la mémoire, soit il est dans un état "récupération d'évènements" à l'écoute de requêtes réseaux.

Si vous souhaitez mettre en ligne un site avec des services workers, il faut que votre serveur soit configuré en HTTPS. Mais comme nous travaillons en local dans le cadre de ce tp cette étape n'est pas nécessaire.

Nous allons nous inspirer de l'exemple trouvé sur GitHub et qui permet d'afficher une page toute simple puis de pouvoir la rafaichir même sans être conencté au réseau. Partez de l'archive suivante pour réaliser l'éxercice.

Le HTML est tout simple. Il fait référence à un fichier image-list.js qui contient les URL des images à afficher et un fichier app.js qui va nous servir à installer le service worker.

    
      <!DOCTYPE html>
      <html>
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <meta name="viewport" content="width=device-width">
        <title>Service worker demo</title>
        <link rel="stylesheet" href="style.css">
      </head>

      <body>
        <h1>Lego Star Wars gallery</h1>
        <section></section>

        <script src="image-list.js"></script>
        <script src="app.js"></script>
      </body>
      </html>
    
  

Dans app.js copiez ce code. Il permet successivement de vérifier que votre navigateur supporte l'exécution d'un service worker puis d’enregistrer votre navigateur auprès du service worker définit dans sw.js. On affiche dans la console les différents cas de figure d'exécution (installation, sw actif, echec, ...).

    var CACHE_NAME = 'my-site-cache-v1';
    if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('sw.js', { scope: '.' }).then(function(reg) {

    if(reg.installing) {
    console.log('Service worker installing');
  } else if(reg.waiting) {
  console.log('Service worker installed');
} else if(reg.active) {
console.log('Service worker active');
}

}).catch(function(error) {
// registration failed
console.log('Registration failed with ' + error);
});
}

Normalement si tout s'est bien passé, un message s'est affiché dans la console et votre service worker est visible dans l'onglet "application". Ce code est a exécuter sur un serveur web.

Nous allons maintenant configurer le service-worker. Dans un premier temps il faut indiquer toutes les ressources qui vont devoir être mises en cache. Complétez le code suivant dans le fichier sw.js.

    
      self.addEventListener('install', function(event) {
      event.waitUntil(
      caches.open('my-site-cache-v1').then(function(cache) {
      return cache.addAll([
      '.',
      'index.html',
      [à compléter]
      'gallery/snowTroopers.jpg'
      ]);
    })
    );
  });


Lorsque la procédure d'installation du service worker est captée (ligne 1) alors la mise en cache peut commencer. La méthode caches.open() permet de définir le nom du cache (ici celui créé dans app.js auquel vous souhaitez associer des données à mettre en cache pour une exécution du site sans connexion web. Si une seule ressource ne se charge pas, le service worker ne sera pas installé.

Dans le service worker il faut également définir les réponses aux requêtes qui peuvent être faites sans connexion (rafraichissement, navigation). Le service worker recevra des évènements de type fetch qu'il faudra gérer avec la méthode caches.match(). Elle regarde si la requête est en cache et retourne un résultat le cas échéant. Si la requète n'est pas en cache, la seconde partie du code sert à mettre en cache de nouvelles requêtes.

  
    self.addEventListener('fetch', function(event) {
    event.respondWith(caches.match(event.request).then(function(response) {
    // caches.match() always resolves
    // but in case of success response will have value
    if (response !== undefined) {
    return response;
  } else {
  return fetch(event.request).then(function (response) {
  // response may be used only once
  // we need to save clone to put one copy in cache
  // and serve second one
  let responseClone = response.clone();

  caches.open('my-site-cache-v1').then(function (cache) {
  cache.put(event.request, responseClone);
});
return response;
}).catch(function () {
return caches.match('gallery/myLittleVader.jpg');
});
}
}));
});

La première version de votre service worker est prête. Nous allons compléter le code de app.js afin que celui-ci génère des requêtes que votre service worker sera à même de pouvoir gérer s'il y a une coupure de réseau.

Regardez le code suivant. Vous reconnaitrez une requête AJAX qui charge les images (depuis un Json). Images qui seront intégrées plus tard dans le HTML. La spécificité de cette requête Ajax est qu'elle est encapsulée dans une promesse qui permet d'appeler de manière asynchrone la suite des traitements tout en gérant la réussite ou l'échec de cette requête.

  

    function imgLoad(imgJSON) {
    // return a promise for an image loading
    return new Promise(function(resolve, reject) {
    var request = new XMLHttpRequest();
    request.open('GET', imgJSON.url);
    request.responseType = 'blob'; //on indique que la réponse sera une donnée binaire (l'image)

    request.onload = function() {
    if (request.status == 200) {
    var arrayResponse = [];
    arrayResponse[0] = request.response;
    arrayResponse[1] = imgJSON;
    resolve(arrayResponse);
    //Promise.resolve()
    //Renvoie un objet Promise qui est tenu (résolu) avec la valeur donnée. 
    //Si la valeur possède une méthode then, la promesse renvoyée « suivra » cette méthode 
    //pour arriver dans son état. (définition developper.mozilla)
  } else {
  reject(Error('Image didn\'t load successfully; error code:' + request.statusText));
}
};

request.onerror = function() {
reject(Error('There was a network error.'));
};

// Send the request
request.send();
});
}

Il ne reste plus qu'à écrire le code permettant, pour entrée dans le JSON (objet Gallery), d'exécuter une requête AJAX de permet de récupérer le contenu binaire de l'image et toutes les informations que l'on souhaite afficher. La suite du code est toute simple. Elle permet d'intégrer les images.

  

    var imgSection = document.querySelector('section');

    window.onload = function() {

    // load each set of image, alt text, name and caption
    for(var i = 0; i<=Gallery.images.length-1; i++) {
    imgLoad(Gallery.images[i]).then(function(arrayResponse) {
    //Si succès alors le "resolves" s'exécutera sur arrayResponse

    var myImage = document.createElement('img');
    var myFigure = document.createElement('figure');
    var myCaption = document.createElement('caption');
    var imageURL = window.URL.createObjectURL(arrayResponse[0]);

    /*COMPLETER LE CODE*/

  }, function(Error) {
  //exécution du reject
  console.log(Error);
});
}
};

Si tout marche bien, votre site devrait ressembler à cela :

Pour tester votre travail en conditions réelles il faudrait que vous ayez déployer votre code sur un serveur implémentant https.
Rien de plus simple : suivez le tutoriel suivant qui explique comment déployer grace à github.
Si vous n'avez pas le temps de le faire, regardez ce que donne ce code sur mon github. Comme sur la vidéo ci-dessous, faites le test montrant que même sans réseau les requêtes sont bien exécutées grâce à la gestion du cache.

J'ai également déployé le même code sur un serveur web en http. Vérifiez ICI que sans le https, le même code ne permet pas de gérer le cache si le réseau est coupé.

Pour aller plus loin sur les services worker, vous pouvez aller regarder les derniers tutoriels proposés par Google

Sauvegarde de données en local


Local Storage : une solution de stockage synchrone

Cette partie est très largement inspirée des (bons) tutoriels de mozilla et d'alsacréation. N'hésitez pas à vous y référer pour compléter vos connaissances.

Si vous devez développer une application (mobile ou web) qui doit s'exécuter dans un contexte hors-ligne. Il est possible de stocker des informations sur la forme de "clé/valeur" de manière plus performante que les cookies (qui sont par exemple limités en taille).

Stockage de session sessionStorage
L'interface sessionStorage mémorise les données sur la durée d'une session de navigation, et sa portée est limitée à la fenêtre ou l'onglet actif. Lors de sa fermeture, les données sont effacées. Contrairement au cookies, il n'y a pas d'interférence. Chaque stockage de session est limité à un domaine.

Stockage local localStorage
L'interface localStorage mémorise les données sans limite de durée de vie. Contrairement à sessionStorage, elles ne sont pas effacées lors de la fermeture d'un onglet ou du navigateur. La portée de localStorage est de facto plus large : il est possible de l'exploiter à travers plusieurs onglets ouverts pour le même domaine ou plusieurs fenêtres ; à partir du moment où il s'agit bien sûr du même navigateur. Il n'y a pas de partage des données Web Storage entre les différents navigateurs qui peuvent être installés sur une même machine.

Comme pour une gestion classique des map, il existe 5 méthodes permettant de manipuler ce stockage local.

Reprenez le code de l'exercice sur l'ajout de personnes dans des listes. Nous allons transformer ce code afin qu'il stocke en local les noms/prénoms saisis. Ainsi à chaque rechargement de la page il faut que les données saisies précédement soient toujours présentes dans les listes. La vidéo illustre ce que vous devez faire.

Etablir la stratégie permettant de sauvegarder les données dans le localStorage et de charger la map avec fait partie de l'exercice.Je vous donne néanmoins quelques pistes. Il existe plusieurs manières de réaliser cet exercices.


<script>
// Détection du support
if(typeof sessionStorage!='undefined') {
    alert("sessionStorage est supporté");
    //des choses à faire pour récupérer des noms dans le localStorage
} else {
  alert("sessionStorage n'est pas supporté");
}
</script>


<script>
userMap=new Map(JSON.parse(localStorage.getItem("listeNoms")));
localStorage.setItem("listeNoms", JSON.stringify(Array.from(userMap.entries()))); 
</script>

LocalForage : une solution de stockage asynchrone

Le problème avec localStorage, c'est que qu'il n'est pas la solution idéale pour la mise en production d'applications où le nombre d'accès et la quantité de données à stockées est important. En effet, localStorage réalise des actions synchrones. Il faut donc attendre que l'écriture ou la lecture en base soit terminée pour pouvoir continuer à exécuter du code JS. Cela peut donc ralentir l'exécution de votre application.

Il existe plusieurs librairies JS permettant de réaliser du localSotrage de manière asynchrone. Une intéressante est localForage. Vous pouvez télécharger cette bibliothèque sur le gitHub officiel et inclure ce code depuis votre page web.



  <script src="localForage-1.7.3/dist/localforage.js"></script>

Par exemple, là où dans l'exercice précédent le chargement du localStorage vers une map était synchrone,


<script>
  userMap=new Map(JSON.parse(localStorage.getItem("listeNoms")));
  // opération suivante 
  //..
  // opération suivante
</script>

Vous pouvez maintenant écrire :


<script>
localforage.getItem('listeNoms').then(function(value) {
      // opération suivante 
      //..
      // opération suivante
    }).catch(function(err) {
        console.log(err);
});
</script>

Modifiez le code de l'exercice précédent pour qu'il fonctionne maintenant en asynchrone.

Etude d'une PWA


Rendez-vous sur l'application météo que j'ai déployée sur mon serveur. Nous allons ensemble étudier comment elle fonctionne.

Cette application est extraite d'un tutoriel sur les PWA créé par Google.