Pour votre progression, c'est très important de finir le TP précédent avant de commencer celui-ci.
Commençons ce TP par des révisions.
Ces révisions sont un peu plus approfondies que les précédentes.
On a déjà vu beaucoup de choses, c'est important de faire un bilan.
Que faut-il écrire pour créer une variable appelée "a" dont la valeur est un booléen vrai ?
Que faut-il écrire (en une seule ligne) pour créer une fonction qui puisse être appelée comme ceci:
var a=age_client("Akima");
(mettez ce que vous voulez dans la fonction)
var nom="jean";
var fichier=XXXX;
Que faut-il écrire à la place de XXXX pour que fichier prenne la valeur "jean.txt" ?
(en utilisant la variable nom)
var a=exemple();Quel est le type contenu dans la variable a ?
function exemple()
{
return true;
}
var a=exemple;Quel est le type contenu dans la variable a ?
function exemple()
{
return true;
}
var a=function()Quel est le type contenu dans la variable a ?
{
return true;
};
$('p').XXXX;
Que faut-il écrire à la place de XXXX pour que
<p>
bonjour
</p>
devienne
<p>
aurevoir
</p>
$('p').XXXX;
Que faut-il écrire à la place de XXXX pour que
<p>
bonjour
</p>
devienne
<p>
<strong>aurevoir</strong>
</p>
console.log($('input').XXXX);
Que faut-il écrire à la place de XXXX pour afficher dans la console le texte tapé par l'utilisateur dans le champs texte suivant:
<p>
<input type="text" />
</p>
console.log($('p').XXXX);
Que
faut-il écrire à la place de XXXX pour afficher "bonjour" dans la console:
<p>
bonjour
</p>
0 $('.boites .non').click(function()
1 {
2
3 });
L'utilisateur clique sur l'élément 7.
A la ligne 2 du programme, quel est le nom de la variable qui désigne l'élément 7 ?
(c'est à dire: l'élément sur lequel on vient de cliquer)
Quand l'utilisateur clique sur 5 ou 7, on voudrait que l'élément cliqué (5 ou 7) soit caché.
0 $('.boites .non').click(function()
1 {
2 $(this).XXXX;
3 });
Que faut-il écrire à la place de XXXX ?
Quand l'utilisateur clique sur 5 ou 7, on voudrait que l'élément au-dessus (2 ou 3) soit caché.
0 $('.boites .non').click(function()Que faut-il écrire à la place de XXXX ?
1 {
2 $(this).XXXX;
3 });
Quand l'utilisateur clique sur 5 ou 7, on voudrait que 4 ou 6 soit caché.
0 $('.boites .non').click(function()
1 {
2 $(this).XXXX;
3 });
Que faut-il écrire à la place de XXXX ?
Si vous venez d'assister au cours magistral, passez à la section suivante.
Dans les exemples vus précédemment, le serveur renvoyait un fragment de HTML qui était ensuite affiché.
Bien souvent, on veut chercher des données complexes sur le serveur, et pas juste du HTML.
On pourrait utiliser n'importe quel format pour ces données. Il suffit juste que le programme sur le serveur et le JavaScript soient d'accord.
Le XML a été beaucoup utilisé, mais est lourd à manipuler.
Pour les tâches simples, du texte brut est possible.
Pour les informations plus structurées on utilise souvent, aujourd'hui, un format appelé JSON.
Le JSON est format texte qui est presqu'identique au format utilisé en JavaScript pour déclarer des Objets ou des Tableaux.
Tous les principaux langages fournissent des méthodes pour encoder et décoder le JSON.
Voici quelques exemples.
En pratique, les données JSON peuvent être volumineuses et complexes
En PHP la fonction json_encode() permet de transformer très simplement un tableau (ou un objet) en JSON. Ce JSON est ensuite envoyé au client, qui pourra le décoder simplement en un objet (ou tableau) JavaScript.
Remarquez la fonction "header()".
Toute réponse HTTP contient des données appelées "entêtes". Attention: il ne s'agit pas des entêtes du HTML, mais des informations, cachées, en dehors de tout code HTML.
Dans ces entêtes HTTP, on doit préciser le type de contenu transmis:
Pour du HTML: Content-type: text/html
Pour du JSON : Content-type: application/json
Voici un exemple simplifié.
Le client demande des données sur l'utilisateur numéro 1234.
Le PHP cherche ces données dans la BDD, les récupère dans un tableau associatif et transforme ce tableau en JSON.
Le JSON est renvoyé au client.jQuery décode le JSON et met l'objet décodé en argument (reponse) de la fonction anonyme.
Remarquez que jQuery est capable de distinguer automatiquement une réponse HTML d'une réponse JSON grâce au Content-Type. Si Content-Type est text/html, reponse sera une chaîne de caractères contenant le HTML. Si c'est application/json, reponse est un objet JS.
Dans l'approche classique, la navigation se fait en cliquant sur des liens ou en appuyant sur des boutons de formulaires. A chaque fois une nouvelle page est visitée. De temps en temps une interaction AJAX peut venir enrichir cette navigation.
À l'autre extrême, dans une application JS, une seule page est chargée et l'utilisateur ne la quitte pas. L'utilisateur interagît avec cette page de manière complexe. Ces interactions sont gérées en JS avec des appels AJAX fréquents.
Un exemple d'application JS est gmail. L'utilisateur change de boite mail et ouvre chaque mail sans jamais quitter la page. Toutes ces interactions sont gérées en JS/AJAX.
Les applications JS sont difficiles à écrire. Le développeur doit gérer de nombreuses interactions complexes avec l'utilisateur et synchroniser les données avec le serveur.
Des Frameworks JS, comme AngularJS existent pour faciliter cette tâche. Ces techniques sortent du cadre de ce cours.
Dans cet exercice, on veut chercher des informations sur un joueur quand on clique sur son nom.
Ces informations (nom, age, score) sont sur le serveur.
Il faut donc utiliser une requête AJAX.
On veut chercher des informations sur un joueur quand on clique sur son nom.
Quelle méthode faut-il utiliser pour la requête AJAX ?Créez les fichiers suivants
utilisateurs.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>utilisateurs</title>
<link type="text/css" rel="stylesheet" href="utilisateurs.css"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="utilisateurs.js"></script>
</head>
<body>
<!--------------------->
<ul id="utilisateurs">
<li data-uid="karim" >Karim </li>
<li data-uid="martin">Martin</li>
<li data-uid="leila" >Leïla </li>
<li data-uid="joe" >Joe C.</li>
</ul>
<div id="affichage">
<p>Nom : <span id="nom" ></span> </p>
<p>Age : <span id="age" ></span> ans </p>
<p>Score : <span id="score"></span> </p>
</div>
<!--------------------->
</body>
</html>
utilisateurs.css
body
{
font-family: sans;
}
#utilisateurs
{
width: 150px;
cursor: pointer;
}
#utilisateurs li:hover
{
background-color: #fea;
}
#affichage
{
background-color: white;
width: 200px;
border: 1px solid #aaa;
box-shadow: 1px 1px 2px rgba(0,0,0,.2);
padding: 10px;
}
#affichage p
{
margin: 4px;
}
#affichage span
{
color: #00a;
}
utilisateurs.php
<?php
// Une liste d'utilisateurs, juste pour l'exemple.
// En pratique on chercherait dans une base de données.
$utilisateurs=array(
'joe' =>array('nom'=>'Joe C.','score'=>34,'age'=>22),
'martin'=>array('nom'=>'Martin','score'=>3 ,'age'=>7 ),
'karim' =>array('nom'=>'Karim' ,'score'=>45,'age'=>19),
'leila' =>array('nom'=>'Leïla' ,'score'=>49,'age'=>23),
);
utilisateurs.js
console.log("Ce programme JS vient d'être chargé");
$(document).ready(function()
{
console.log("Le document est pret");
// ......
console.log("La mise en place est finie. En attente d'événements...");
});
Faites en sorte que le programme affiche dans la console le uid de l'utilisateur sur lequel on a cliqué.
console.log("Ce programme JS vient d'être chargé");
$(document).ready(function()
{
console.log("Le document est pret");
$('#utilisateurs li').mousedown(function(e)
{
console.log("Évènement mousedown");
console.log("uid:",$(this).attr('data-uid'));
});
console.log("La mise en place est finie. En attente d'événements...");
});
Vous devriez voir toutes les requêtes faites lors du chargement de la page.
Il y en a une vingtaine.
La toute première requête correspond à la page HTML elle même.
Les autres sont pour l'essentiel du CSS, du JS et des images.
Cliquez sur le + à gauche de cette première requête.
Cette requête est constituée de :
La requête (du client vers le serveur) est constituée uniquement d'entêtes.
C'est une série d'informations de la forme "nom: valeur".
La réponse est constituée d'entêtes et d'un corps.
Ces entêtes sont aussi de la forme "nom: valeur".
Le corps de la réponse c'est le HTML lui même.
Prenez le temps de regarder les différents onglets pour bien comprendre.
Ces entêtes sont "invisibles" pour l'utilisateur et pour le développeur débutant.
Cependant, elles contiennent des informations souvent importantes.
Dans les entêtes de la réponse de http://moodle.iutv.univ-paris13.fr, quelle est la valeur de "Content-type" ?
L'entête "Content-type" permet au serveur de dire au client :
« Le corps de la réponse contient des données au format XYZ »
Content-type est un type MIME
Dans cet exercice le serveur veut envoyer du JSON au client.
En regardant sur votre cours (ou sur le web), quel type MIME faut-il utiliser ?
Ouvrez l'url suivante dans votre navigateur:
http://moodle.iutv.univ-paris13.fr/img/js/revision6-boites.png
Ouvrez l'onglet Réseau de Firebug et rechargez la page complètement (maj+click sur bouton rechargement).
Regardez les entêtes http de la réponse.
Quel est le Content-Type ?
(n'oubliez pas de forcer le rechargement avec maj+click bouton recharger)
{"nom":"Karim","score":45,"age":19,"status":"ok"}
header('Content-type: application/json');
utilisateurs.php
<?php
// Une liste d'utilisateurs, juste pour l'exemple.
// En pratique on chercherait dans une base de données.
$utilisateurs=array(
'joe' =>array('nom'=>'Joe C.','score'=>34,'age'=>22),
'martin'=>array('nom'=>'Martin','score'=>3 ,'age'=>7 ),
'karim' =>array('nom'=>'Karim' ,'score'=>45,'age'=>19),
'leila' =>array('nom'=>'Leïla' ,'score'=>49,'age'=>23),
);
$uid=$_GET['uid'];
$reponse=$utilisateurs[$uid];
header('Content-type: application/json');
echo json_encode($reponse);
Dans les outils de développement Firefox vérifiez que le Content-Type renvoyé par utilisateurs.php est bien "application/json".
Maintenant on peut compléter utilisateurs.js :
Faites une requête AJAX pour récupérer les informations sur l'utilisateur cliqué et affichez la réponse avec console.log()
Vérifiez que la réponse est bien un objet (l'objet affiché par console.log() devrait être en vert et vous pouvez cliquer dessus pour le voir dans l'onglet DOM de Firebug).
Ensuite remplissez les différents <span> de l'affichage (<div id="affichage">...</div>) avec les valeurs récupérées.
utilisateurs.js
console.log("Ce programme JS vient d'être chargé");
$(document).ready(function()
{
console.log("Le document est pret");
$('#utilisateurs li').mousedown(function(e)
{
console.log("Évènement mousedown");
$.get('http://localhost/~1234567/tp-6/utilisateurs.php',
{uid: $(this).attr('data-uid')},
function(reponse)
{
console.log("Réponse reçue du serveur: ",reponse);
$('#nom' ).text(reponse.nom);
$('#age' ).text(reponse.age);
$('#score').text(reponse.score);
});
});
console.log("La mise en place est finie. En attente d'événements...");
});
Ajoutez la ligne suivante à utilisateurs.html
<li data-uid="nath" >Nath.</li>
Cet utilisateur n'existe pas dans utilisateurs.php
On veut afficher un alert() avec un message d'erreur quand on clique sur un utilisateurs qui n'existe pas.
Faites la vérification nécessaire dans utilisateurs.php
Dans l'objet JSON renvoyé, ajoutez un champs "ok" qui peut être vrai ou faux.
Ajoutez aussi un champs "message" qui peut contenir un message d'erreur.
Faîtes les modifications nécessaires dans utilisateurs.js
utilisateurs.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>utilisateurs-1</title>
<link type="text/css" rel="stylesheet" href="utilisateurs.css"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="utilisateurs.js"></script>
</head>
<body>
<!--------------------->
<ul id="utilisateurs">
<li data-uid="karim" >Karim </li>
<li data-uid="martin">Martin</li>
<li data-uid="tom" >Tom </li>
<li data-uid="leila" >Leïla </li>
<li data-uid="joe" >Joe C.</li>
<li data-uid="nath" >Nath.</li>
</ul>
<div id="affichage">
<p>Nom : <span id="nom" ></span> </p>
<p>Age : <span id="age" ></span> ans </p>
<p>Score : <span id="score"></span> </p>
</div>
<!--------------------->
</body>
</html>
utilisateurs.js
console.log("Ce programme JS vient d'être chargé");
$(document).ready(function()
{
console.log("Le document est pret");
$('#utilisateurs li').mousedown(function(e)
{
console.log("Évènement mousedown");
$.get('http://localhost/~1234567/tp-6/utilisateurs.php',
{uid: $(this).attr('data-uid')},
function(reponse)
{
console.log("Réponse reçue du serveur: ",reponse);
if(reponse.ok)
{
$('#nom' ).text(reponse.nom);
$('#age' ).text(reponse.age);
$('#score').text(reponse.score);
}
else
{
alert(reponse.message);
}
});
});
console.log("La mise en place est finie. En attente d'événements...");
});
utilisateurs.php
<?php
// Une liste d'utilisateurs, juste pour l'exemple.
// En pratique on chercherait dans une base de données.
$utilisateurs=array(
'joe' =>array('nom'=>'Joe C.','score'=>34,'age'=>22),
'martin'=>array('nom'=>'Martin','score'=>3 ,'age'=>7 ),
'karim' =>array('nom'=>'Karim' ,'score'=>45,'age'=>19),
'leila' =>array('nom'=>'Leïla' ,'score'=>49,'age'=>23),
);
$uid=$_GET['uid'];
if(isset($utilisateurs[$uid]))
{
$reponse=$utilisateurs[$uid];
$reponse['ok']=true;
}
else
{
$reponse=array('ok'=>false,
'message'=>"Cet utilisateur n'existe pas");
}
header('Content-type: application/json');
echo json_encode($reponse);
On voudrait afficher la boite <div id="affichage">...</div> comme un popup.
C'est à dire : la placer à l'endroit où l'utilisateur à cliqué.
Modifiez utilisateurs.css et utilisateurs.js pour obtenir ce résultat.
on peut, par exemple, utiliser :
Fermeture de la boite:
Option simple : on veut fermer le popup quand on clique dessus.
Option plus compliquée (pas indispensable) : on ferme le popup si on clique n'importe où dans l'écran (sauf sur un utilisateur).
Quand un élément (ici l'image) a « position: absolute », il n'affecte plus du tout le positionnement des autres éléments ou texte autour. Il s'affiche donc « par-dessus » ou « par-dessous » les éléments l'entourant.
Tout seul « position: absolute » n'est pas très utile. Par contre, une fois qu'un élément est « position: absolute », on peut le déplacer facilement avec les propriétés « top, left, right, bottom »:
utilisateurs.css
...
#affichage
{
position: absolute;
display: none;
...
utilisateurs.js
console.log("Ce programme JS vient d'être chargé");
$(document).ready(function()
{
console.log("Le document est pret");
$('#utilisateurs li').mousedown(function(e)
{
console.log("Évènement mousedown sur li");
$('#affichage').hide();
$.get('http://localhost/~1234567/tp-6/utilisateurs.php',
{uid: $(this).attr('data-uid')},
function(reponse)
{
console.log("Réponse reçue du serveur: ",reponse);
if(reponse.ok)
{
$('#nom' ).text(reponse.nom);
$('#age' ).text(reponse.age);
$('#score').text(reponse.score);
$('#affichage').show();
$('#affichage').offset({left: e.pageX,top:e.pageY});
}
else
{
alert(reponse.message);
}
});
});
// Fermer la boite si on clique n'importe où dans la page (sauf sur un utilisateur)
$('html').mousedown(function(e)
{
console.log("Évènement mousedown sur html");
if(!$(e.target).is('#utilisateurs li'))
{
$('#affichage').hide();
}
});
console.log("La mise en place est finie. En attente d'événements...");
});
Si vous venez d'assister au cours magistral, passez à la section suivante.
Jusqu'ici on a écrit nos programmes en utilisant presqu'exclusivement jQuery. jQuery est une surcouche par-dessus le DOM. Le DOM est l'API native permettant d’interagir avec le navigateur. jQuery n'est pas indispensable.
jQuery facilite énormément le travail. Non seulement le code est beaucoup plus simple et court à écrire, mais en plus, jQuery s'occupe des différences entre navigateurs (ie, Firefox, Chrome, ...).
Cependant, jQuery a un prix:- le téléchargement de jQuery peut prendre quelque temps.- le traitement / exécution de jQuery peut être long sur des machines peu puissantes (mobile 400ms!).
Même si on utilise jQuery, on a parfois besoin d'accéder à des propriétés ou des fonctions du DOM.
La manipulation de texte nécessite de manipuler les Text Node du DOM.
Dans des environnements où jQuery n'est pas disponible (ex: extension Firefox, page critique performance).
Le développement JS nécessite de vérifier en permanence le bon fonctionnement du code sur les principaux navigateurs du public visé. jQuery masque beaucoup de différences.
Le support des versions anciennes de ie ou certains mobiles peut-être une difficulté majeure qui peut modifier profondément des choix de développement.
Des différences, moindres, mais significatives existent entre Chrome, Firefox, Safari et aussi entre entre différentes versions de chacun de ces navigateurs.
Des sites comme caniuse.com ou MDN fournissent des tables de compatibilité pour chaque technologie et pour chaque navigateur.
Il est indispensable de consulter ces tables quand on utilise une fonctionnalité (JS ou CSS), surtout si la fonctionnalité est récente.
Voici 3 types d'objets DOM importants:
- window : la fenêtre d'un document. S'il y a plusieurs onglets, chaque onglet a son window.
- document : à l'intérieur du window, contient l'arbre DOM issu du HTML.
- HTMLElement : la plupart des noeuds de l'arbre qu'on manipulera sont de type HTMLElement
L'objet "window" contient de nombreuses propriétés et méthodes. L'une d’entre-elles s’appelle "document" et donne accès à l’arbre DOM.
document est la racine de l'arbre DOM. Il ne figure pas dans le code HTML. Dans le DOM, il se trouve au-dessus de l'élément <html>.
document fourni aussi de nombreuses propriétés et méthodes. Certaines permettent d'accéder aux éléments de l'arbre DOM.
La fonction getElementById() permet de chercher l'élément ayant un id fourni en argument. Par définition cet élément est unique.
Ici "d" est un objet DOM.
L'équivalent en jQuery est $('#photo').
Remarquez que le type est différent. "j" est une liste jQuery (avec un seul élément), alors que "d" est un objet DOM.
On verra plus tard que l'on peut passer d'un type à l'autre.
La fonction .createElement() permet de créer un élément.
Il est important de bien garder à l'esprit que cet élément n'est pas encore inséré dans l'arbre DOM. Pour l'instant il existe tout seul, sans parents, ni enfants.
jQuery permet d'obtenir un résultat similaire avec $('<p></p>'). jQuery est beaucoup plus puissant: on peut spécifier du code HTML complexe et donc créer directement de nombreux éléments.
Une fois de plus, "j" est une liste jQuery (avec un seul élément), alors que "d" est un objet DOM.
Une fois qu'un élément est crée, on peut l'insérer dans l'arbre DOM.
La fonction DOM appendChild() permet de le faire. Remarquez que dans cet exemple on utilise document.body qui permet d’accéder directement à l'élément <body>.
En jQuery, on obtient un résultat similaire avec .append()
On peut facilement passer d'une liste jQuery contenant un seul élément à l'objet DOM.
Le DOM est une norme indépendante du langage de programmation qui défini une hiérarchie de classes (la notion de classe n’existe pas telle quelle en JS).
La plupart du temps on manipulera des objets DOM de type HTMLElement.
Node est l'interface définissant les opérations d'arbre (parent, enfants, frères).
Text représente du texte.
Un HTMLElement "est un" Node.
Un document "est un" Node.Un Text "est un" Node.
Ces trois types peuvent donc être insérés dans un arbre.
Dans les schémas d'arbre DOM on omet souvent de montrer le texte.
En réalité, dans l'arbre DOM, le texte est contenu dans des Text Node.
On peut manipuler les Text Node comme les autres Node.
jQuery ne fourni qu'un accès très limité aux Text Node.
Quand on veut manipuler du texte (par exemple couper le texte d'un paragraphe en deux paragraphes), on utilise le DOM plutôt que jQuery.
Node et HTMLElement fournissent de très nombreuse propriétés et méthodes.
En HTML, l'attribut class peut contenir plusieurs classes séparées par des espaces.
Pour accéder à la classe d'un élément DOM on utilise className (en effet, "class" est un nom réservé du JS).
className donne la classe en entier. Ce n'est pas très pratique pour manipuler des classes multiples.
jQuery fourni des fonctions beaucoup plus pratiques, pour vérifier, ajouter et retirer des classes.
Le parcours de l'arbre DOM est une opération de base, très fréquente.
Ici on veut remonter au parent d'un élément.
Les fils directs (fils immédiats) sont donnés par .children ou .childNodes ".children" omet les Text Nodes.
Ces deux propriétés donnent une liste, qui peut-être manipulée comme un tableau (liste.length liste[n] ...)
La fonction addEventListener permet d'ajouter un gestionnaire d'événements à un élément. (Rappel: le gestionnaire d'événements est une fonction qui est appelée quand un événement survient.)
addEventListener() ajoute un gestionnaire à un seul élément. Il faut donc appeler addEventListener() sur chaque élément visé.
L'approche jQuery est beaucoup plus pratique.
On va commencer cette partie avec quelques questions sur le cours précédent.
Quel est le navigateur le plus utilisé en ce moment (2018) ?
En moyenne, quel pourcentage des visiteurs d'un site web sont sur un navigateur mobile ?
(nombre approximatif entre 0 et 100)
A partir de quelle version de Internet Explorer (IE) peut-on utiliser la fonctionnalité appelée " XMLHttpRequest" (level 2 / advanced features) ?
Vous pouvez chercher sur :
http://caniuse.com/
<input type="color"/>
(exemple d'affichage à droite)
Dans l'arbre DOM, quel est l'élément parent de l'élément <body> ?
(juste le nom)
Dans l'arbre DOM, quel est l'élément parent de l'élément <html> ?
(juste le nom)Quel est l'équivalent DOM de l'expression jQuery suivante :
$('#affichage')
var d=document.getElementById('affichage');
XXXX
Que faut-il écrire en DOM à la place de XXXX pour transformer ceci:
<div id="affichage">...</div>
en ceci:
<div id="affichage" class="urgent">...</div>
(sans utiliser jQuery)
var d=document.getElementById('affichage');
XXXX
Que faut-il écrire en DOM à la place de XXXX pour transformer ceci:
<div>
<div id="affichage">...</div>
</div>
en ceci:
<div class="urgent">
<div id="affichage">...</div>
<div>
(sans utiliser jQuery)
Indication: il faut enchaîner des opérations.
var d=document.getElementById('affichage');
XXXX
Que faut-il écrire en DOM à la place de XXXX pour transformer ceci:
<div id="affichage">
<span></span>
<span></span>
<span></span>
</div>
en ceci:
<div id="affichage">
<span></span>
<span class="urgent"></span>
<span></span>
</div>
(sans utiliser jQuery)
Indication: il faut enchaîner des opérationsvar d=document.getElementById('affichage');
XXXX
function reagir_click()
{
console.log("L'utilisateur a cliqué sur le div");
}
HTML:
<div id="affichage"></div>
Dans les pages suivantes, nous allons écrire, ensemble, un premier programme DOM très simple.
Créez les deux fichiers suivants:
hello-dom.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>hello-dom</title>
<script src="hello-dom.js"></script>
</head>
<body>
<!--------------------->
<h1 id="titre">Hello DOM world !</h1>
<!--------------------->
</body>
</html>
hello-dom.js
console.log("Ce programme JS vient d'être chargé");
En jQuery on écrivait :
$(document).ready(function(){ ... });
Ceci dit au navigateur : « appelle ma fonction quand le document sera prêt ».
Si on n'attendait pas, le JS serait exécuté tout de suite, avant même que l'arbre DOM soit construit.
On ne pourrais donc pas accèder à l'arbre DOM...
"ready" n'est pas un "vrai" événement DOM. C'est une simplification de jQuery.
En DOM on utilise un événement appelée "DOMContentLoaded" .
On a vu en cours la fonction "addEventListener" qui permet de gérer les événements.
Complétez hello-dom.js pour afficher "Le document est pret" dans la console, quand le document est pret.
hello-dom.js
console.log("Ce programme JS vient d'être chargé");
document.addEventListener("DOMContentLoaded", function()
{
console.log("Le document est pret");
});
Remarque: ceci ne marche pas sur IE 8 ou antérieur.
Quand l'utilisateur clique sur le titre, on voudrait changer sa couleur en rouge.
Les éléments DOM ont une propriété appelée style qui correspond à l'attribut style de l'élément HTML.
Par exemple :
<div id="exemple">...</div>
Avec :
var d=document.getElementById('exemple');
d.style.margin="20px";
le HTML devient
<div id="exemple" style="margin: 20px">...</div>
Pour les propriétés CSS ayant des noms contenant des tirets, on utilise soit:
d.style.marginLeft="20px";
soit
d.style['margin-left']="20px";
console.log("Ce programme JS vient d'être chargé");
document.addEventListener("DOMContentLoaded", function()
{
console.log("Le document est pret");
var titre=document.getElementById('titre');
titre.addEventListener("mousedown", function(e)
{
console.log("Le bouton de la souris a été enfoncé.");
this.style.color="red";
});
});
Dans les pages suivantes, nous allons re-écrire, ensemble, le programme morpion, sans utiliser jQuery.
Créez les 3 fichiers suivants:
morpion.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>morpion</title>
<link type="text/css" rel="stylesheet" href="morpion.css"/>
<script src="morpion.js"></script>
</head>
<body>
<!--------------------->
<table id="morpion">
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
</table>
<!--------------------->
</body>
</html>
morpion.css
#morpion
{
border-collapse: collapse;
}
#morpion td
{
border: 1px solid black;
width: 40px;
height: 40px;
font-size: 30px;
text-align: center;
cursor: pointer;
}
#morpion td:hover
{
background-color: #ffc;
}
morpion.js
console.log("Ce programme JS vient d'être chargé");
document.addEventListener("DOMContentLoaded", function()
{
console.log("Le document est pret");
var joueur="X";
});
En jQuery nous avions géré l'événement mousedown sur les cases du tableau comme ceci:
$('#morpion td').mousedown(function(){...});
A combien d'éléments est affecté le gestionnaire d'événements ?
En effet, en interne, jQuery parcourt toute la liste des 9 cases et installe appelle addEventListener sur chaque case.
C'est un peu lourd à faire en DOM.
On va utiliser une astuce : le bubbling.
On va utiliser un seul addEventListener sur le tableau lui-même.
Les événements sur les cases remontent (bubbling) jusqu'au tableau.
Pensez à utiliser "event.target" et non pas "this"
Inspirez vous de la version jQuery.
Indication:
pour lire ou modifier le texte dans un élément, utilisez element.textContent
morpion.js
console.log("Ce programme JS vient d'être chargé");
document.addEventListener("DOMContentLoaded", function()
{
console.log("Le document est pret");
var joueur="X";
document.getElementById('morpion').addEventListener("mousedown", function(e)
{
console.log("Le bouton de la souris a été enfoncé.");
var td=e.target;
if(td.textContent!==''){return;}
td.textContent=joueur;
joueur=(joueur==="X" ? 'O' : 'X');
});
});
Dans les pages suivantes, on va créer, ensemble, un formulaire AJAX permettant d'ajouter un commentaire dans un forum.
(On utilisera jQuery)
Créez les fichiers suivants :
(Les parties modifiées par rapport au TP-5 sont en noir)
commentaires.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>commentaires</title>
<link type="text/css" rel="stylesheet" href="commentaires.css"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="commentaires.js"></script>
</head>
<body>
<!--------------------->
<div id="principal">
<h2>Cours de JavaScript : Ajax</h2>
<img src="http://moodle.iutv.univ-paris13.fr/img/js/exo-commentaires-video.png"/>
</div>
<div id="block-commentaires">
<h3>Commentaires</h3>
<select id="pages">
<option value="0" > 1 à 5</option>
<option value="5" > 6 à 10</option>
<option value="10">11 à 15</option>
<option value="15">16 à 20</option>
</select>
<div id="commentaires">
</div>
<a id="reinitialiser" href="commentaires.php?reinitialiser">réinitialiser</a>
</div>
<div id="formulaire">
<p><label>Nom : <input id="nom" type="text"/></label></p>
<p><label>Commentaire :<br/><textarea id="contenu" ></textarea></p>
<p><input id="ajouter" type="button" value="ajouter"/></p>
</div>
<!--------------------->
</body>
</html>
commentaires.css
body
{
font-family: sans;
width: 600px;
margin-left: auto;
margin-right: auto;
background-color: #eee;
}
#principal
{
background-color: white;
padding: 10px;
}
#principal img
{
width: 580px;
height: auto;
}
#block-commentaires
{
background-color: white;
min-height: 300px;
padding: 10px;
margin-top: 10px;
font-size: 14px;
}
#pages-commentaires
{
}
#commentaires
{
margin-top: 10px;
}
.commentaire
{
margin: 19px 0;
}
.commentaire .nom
{
color: #2793E6;
font-weight: bold;
margin-right: 13px;
}
.commentaire .date
{
color: #888;
}
.commentaire .contenu-com
{
margin: 3px 0;
}
.commentaire .pied-com
{
font-size: 11px;
color: #888;
line-height: 20px;
}
.commentaire .repondre
{
font-weight: bold;
}
.commentaire .jaime
{
font-size: 13px;
color: #2793E6;
}
.commentaire .jaime-plus
{
display: inline-block;
width: 18px;
height: 18px;
background-image: url(http://moodle.iutv.univ-paris13.fr/img/js/ytp.png);
vertical-align: middle;
}
.commentaire .jaime-moins
{
display: inline-block;
width: 18px;
height: 18px;
background-image: url(http://moodle.iutv.univ-paris13.fr/img/js/ytm.png);
vertical-align: middle;
}
#reinitialiser
{
font-size: 10px;
float: right;
}
#formulaire
{
background-color: white;
width: 300px;
border: 1px solid #aaa;
box-shadow: 1px 1px 2px rgba(0,0,0,.2);
padding: 10px;
}
#formulaire p
{
margin: 5px 0;
}
#nom
{
width: 230px;
}
#contenu
{
width: 100%;
}
#contenu
{
width: 290px;
}
commentaires.js (pas de modification)
console.log("Ce programme JS vient d'être chargé");
$(document).ready(function()
{
console.log("Le document est pret");
$('#pages').change(function()
{
$.get('http://localhost/~1234567/tp-6/commentaires.php',
{debut: parseInt($('#pages').val()),
fin: parseInt($('#pages').val())+4,
},
function(reponse)
{
console.log("Une réponse a été reçue du serveur");
$('#commentaires').html(reponse);
});
});
$('#pages').change();
$('#commentaires').on('mousedown','.jaime-plus',function(e)
{
// éviter la sélection désagréable quand on clique
e.preventDefault();
var commentaire=$(this).parent().parent();
var idCommentaire=parseInt(commentaire.attr('data-com-id'));
$.post('http://localhost/~1234567/tp-6/commentaires-jaime.php',
{
id: idCommentaire,
sens: 1,
},
function(reponse)
{
console.log('Réponse recue:',reponse);
if(reponse==='ok')
{
var jaime=commentaire.find('.jaime');
var val=jaime.text()==='' ? 0 : parseInt(jaime.text());
jaime.text(val+1);
}
});
});
});
commentaires.php (pas de modification)
<?php
session_start();
setlocale(LC_TIME, "fr_FR.utf8");
header('Content-Type: text/html; charset=utf-8');
require_once 'commentaires-fonctions.php';
// Cas particulier: réinitialiser les commentaires
if(isset($_GET['reinitialiser']))
{
reinitialiser_commentaires();
die('Commentaires réinitialisés ok.');
}
// Chercher tous les commentaires et trier
$commentaires=commentaires();
$commentaires=trier_par_date($commentaires);
// Lire les données GET envoyées par le client
$debut=(int)$_GET['debut'];
$fin =(int)$_GET['fin' ];
// Construire le HTML à envoyer au client
$html='';
for($i=$debut;$i<=$fin;$i++)
{
$com=$commentaires[$i];
$html.=commentaire_rendu_en_html($com);
$html.="\n";
}
// Envoyer le HTML au client
echo $html;
commentaires-fonctions.php (pas de modification)
<?php
// Racourci pour éviter les problèmes d'affichage des caractères HTML (<>&'") et les failles de sécurité XSS
function e($texte)
{
return htmlspecialchars($texte,ENT_QUOTES,'UTF-8');
}
// Trier par date déscendante
function trier_par_date($c)
{
usort($c,'s');
return $c;
}
function s($a, $b){return $a['date'] == $b['date'] ? 0 : (($a['date'] < $b['date']) ? 1 : -1);}
// La liste des commentaires.
// Les commentaires sont stockés dans la variable de SESSION.
// C'est pratique pour le TP, ca nous évite d'avoir besoin d'une base de données.
// Bien évidemment, ca ne marche pas du tout pour une vraie application.
function commentaires()
{
if(!isset($_SESSION['commentaires']))
{
$_SESSION['commentaires']=liste_initiale_commentaires();
}
return $_SESSION['commentaires'];
}
function reinitialiser_commentaires()
{
$_SESSION['commentaires']=liste_initiale_commentaires();
}
// Enregister la liste des commentaires.
// Les commentaires sont stockés dans la variable de SESSION.
// C'est pratique pour le TP, ca nous évite d'avoir besoin d'une base de données.
// Bien évidemment, ca ne marche pas du tout pour une vraie application.
function enregistrer_commentaires($commentaires)
{
$_SESSION['commentaires']=$commentaires;
}
function liste_initiale_commentaires()
{
return
array(
array('id'=>101,'nom'=>'Karim','date'=>1425283400,'contenu'=>"J'aime beaucoup ces cours de JavaScript !",'jaime'=>5),
array('id'=>102,'nom'=>'Joe' ,'date'=>1425283700,'contenu'=>"Ah bon ? Moi je préfère les tartes aux fraises.",'jaime'=>-1),
array('id'=>103,'nom'=>'Karim','date'=>1425284060,'contenu'=>"T'es pas sur Marmiton là...",'jaime'=>-4),
array('id'=>104,'nom'=>'Driss','date'=>1425284080,'contenu'=>"T'as compris quelque chose à Ajax ?",'jaime'=>0),
array('id'=>105,'nom'=>'Lian' ,'date'=>1425284120,'contenu'=>"Ouais... c'est facile avec jQuery.",'jaime'=>2),
array('id'=>106,'nom'=>'Naïma','date'=>1425284320,'contenu'=>"Ces trucs de client / serveur ca m'embrouille.",'jaime'=>0),
array('id'=>107,'nom'=>'Lian' ,'date'=>1425284520,'contenu'=>"C'est simple... le client c'est le JS ... et le serveur le PHP.",'jaime'=>1),
array('id'=>108,'nom'=>'Naïma','date'=>1425284620,'contenu'=>"Oui, mais le HTML, il est où ?",'jaime'=>0),
array('id'=>109,'nom'=>'Driss','date'=>1425284820,'contenu'=>"Dans la base de données, évidemment.",'jaime'=>-8),
array('id'=>110,'nom'=>'Karim','date'=>1425284920,'contenu'=>"Mais non! T'as rien compris.",'jaime'=>-1),
array('id'=>111,'nom'=>'Lian' ,'date'=>1425285120,'contenu'=>"Le HTML est envoyé par le serveur au navigateur.",'jaime'=>1),
array('id'=>112,'nom'=>'Driss','date'=>1425285180,'contenu'=>"Elle sert à quoi alors la BDD ?",'jaime'=>0),
array('id'=>113,'nom'=>'Karim','date'=>1425285120,'contenu'=>"Le PHP prend les infos de la BDD, fait du HTML avec et l'envoie au navigateur.",'jaime'=>5),
array('id'=>114,'nom'=>'Joe' ,'date'=>1425285220,'contenu'=>"J'ai faim.",'jaime'=>-5),
array('id'=>115,'nom'=>'Naïma','date'=>1425285320,'contenu'=>"C'est bientot la pause ?",'jaime'=>2),
array('id'=>116,'nom'=>'Joe' ,'date'=>1425285420,'contenu'=>"Encore 5 minutes...",'jaime'=>0),
array('id'=>117,'nom'=>'Lian' ,'date'=>1425285620,'contenu'=>"Vous pensez qu'à manger.",'jaime'=>0),
array('id'=>118,'nom'=>'Karim','date'=>1425285720,'contenu'=>"C'est la pause :-)",'jaime'=>10),
array('id'=>119,'nom'=>'Lian' ,'date'=>1425285750,'contenu'=>"Moi je reste travailler.",'jaime'=>-4),
array('id'=>120,'nom'=>'Naïma','date'=>1425285770,'contenu'=>"Chacun son truc...",'jaime'=>0),
);
}
// Renvoyer l'indice dans le tableau du commentaire ayant l'id $id
function commentaires_chercher_cle_de_id($commentaires,$id)
{
foreach($commentaires as $k=>$c){if($c['id']==$id){return $k;}}
return false;
}
// Retourne le HTML nécessaire pour afficher un seul commentaire.
function commentaire_rendu_en_html($com)
{
return
'<div class="commentaire" data-com-id="'.$com['id'].'">'."\n".
' <div class="entete-com">'."\n".
' <span class="nom" >'.e($com['nom']).'</span>'."\n".
' <span class="date">'.strftime('%e %b, %H:%M',$com['date']).'</span></div>'."\n".
' <div class="contenu-com">'.e($com['contenu']).'</div>'."\n".
' <div class="pied-com">'."\n".
' <span class="repondre">Répondre</span> '."\n".
' <span class="jaime">'.($com['jaime']!==0 ? $com['jaime'] : '').'</span> '."\n".
' <span class="jaime-plus"></span> '."\n".
' <span class="jaime-moins"></span> '."\n".
' </div>'."\n".
'</div>'."\n";
}
commentaires-jaime.php (pas de modification)
<?php
session_start();
require_once 'commentaires-fonctions.php';
// Chercher tous les commentaires
$commentaires=commentaires();
$id=(int)$_POST['id'];
$cle=commentaires_chercher_cle_de_id($commentaires,$id);
if($cle===false){echo 'erreur';exit(1);}
$sens=(int)$_POST['sens'];
if($sens!==1 && $sens!==-1){echo 'erreur';exit(1);}
$commentaires[$cle]['jaime']+=$sens;
enregistrer_commentaires($commentaires);
echo 'ok';
commentaires-ajouter.php (nouveau fichier)
<?php
session_start();
require_once 'commentaires-fonctions.php';
// Chercher tous les commentaires
$commentaires=commentaires();
// ...
enregistrer_commentaires($commentaires);
echo 'ok';
Dans ces exercices, travaillez toujours sur le serveur, y compris pour les fichiers html
http://localhost/~1234567/.../commentaires.html
Actuellement, le formulaire est affiché en bas de la page.
On voudrait qu'il soit caché par défaut.
Quand l'utilisateur clique sur un des <span> "Répondre", on voudrait afficher le formulaire dnas un "popup" à l'endroit où l'utilisateur à cliqué.
En vous inspirant de l'exercice au début du TP-6, ajoutez le CSS et le JS nécessaire.
Souvenez-vous que les commentaires affichés sont crées après le démarrage de la page.
Vous ne pouvez donc pas ajouter directement ajouter un gestionnaire d'événements aux <span> "Répondre".
Utilisez la fonction .on() vue au TP-5 (il y a déjà un exemple dans commentaires.js)
Comme le même formulaire peut-être réutilisé, pensez à effacer son contenu avant de l'afficher.
commentaires.css
...
#formulaire
{
position: absolute;
display: none;
...
commentaires.js
...
$('#commentaires').on('mousedown','.repondre',function(e)
{
$('#nom').val('');
$('#contenu').val('');
$('#formulaire').show();
$('#formulaire').offset({left: e.pageX,top: e.pageY});
});
...
Quand l'utilisateur clique sur le bouton ajouter, on voudrait envoyer le contenu du formulaire au serveur en utilisant une requête AJAX.
Quel méthode faut-il utiliser ?
Quand l'utilisateur clique sur le bouton ajouter, on voudrait envoyer le contenu du formulaire au serveur en utilisant une requête AJAX.
On doit :
1) Faire une requête POST vers commentaires-ajouter.php
2) Envoyer dans cette requête les données contenus dans les champs nom et contenu du formulaire
3) Quand la réponse du serveur arrive on doit cacher le formulaire et re-afficher les commentaires (utilisez la même astuce qu'au démarrage de la page)
Dans commentaires-ajouter.php on doit :
1) Recevoir les données POST
2) Créer un nouveau commentaire avec les données nécessaires
3) ajouter ce commentaire à la liste de tous les commentaires
Indications pour l'étape serveur 2) :
Utilisez la fonction PHP time() pour obtenir l'heure actuelle (en secondes depuis le 1/1/1970)
Pour le champs id, calculez l'id maximum de tous les commentaires existants et ajoutez 1.
commentaires.js
console.log("Ce programme JS vient d'être chargé");
$(document).ready(function()
{
console.log("Le document est pret");
$('#pages').change(function()
{
$.get('http://localhost/~1234567/tp-6/commentaires.php',
{debut: parseInt($('#pages').val()),
fin: parseInt($('#pages').val())+4,
},
function(reponse)
{
console.log("Une réponse a été reçue du serveur");
$('#commentaires').html(reponse);
});
});
$('#pages').change();
$('#commentaires').on('mousedown','.jaime-plus',function(e)
{
// éviter la sélection désagréable quand on clique
e.preventDefault();
var commentaire=$(this).parent().parent();
var idCommentaire=parseInt(commentaire.attr('data-com-id'));
$.post('http://localhost/~1234567/tp-6/commentaires-jaime.php',
{
id: idCommentaire,
sens: 1,
},
function(reponse)
{
console.log('Réponse recue:',reponse);
if(reponse==='ok')
{
var jaime=commentaire.find('.jaime');
var val=jaime.text()==='' ? 0 : parseInt(jaime.text());
jaime.text(val+1);
}
});
});
$('#commentaires').on('mousedown','.repondre',function(e)
{
// Seulement bouton souris gauche
if(e.which!==1){return;}
$('#nom').val('');
$('#contenu').val('');
$('#formulaire').show();
$('#formulaire').offset({left: e.pageX,top: e.pageY});
});
$('#ajouter').click(function(e)
{
$.post('http://localhost/~1234567/tp-6/commentaires-ajouter.php',
{
nom: $('#nom').val(),
contenu: $('#contenu').val()
},
function(reponse)
{
$('#formulaire').hide();
$('#pages').change();
});
});
});
commentaires-ajouter.php
<?php
session_start();
require_once 'commentaires-fonctions.php';
// Chercher tous les commentaires
$commentaires=commentaires();
$nom =$_POST['nom'];
$contenu=$_POST['contenu'];
$maxId=0;
foreach($commentaires as $commentaire){$maxId=max($maxId,$commentaire['id']);}
$commentaire=array('id'=>$maxId+1,
'nom'=>$nom,
'date'=>time(),
'contenu'=>$contenu,
'jaime'=>0);
$commentaires[]=$commentaire;
enregistrer_commentaires($commentaires);
echo 'ok';