LIFAP5 - TP 3/4 : Interactions serveur asynchrones
Comme dans les TP précédents, il faut télécharger et ouvrir localement les fichiers suivants, l’ensemble des fichiers est disponible dans l’archive LIFAP5-TP3-fichiers.zip
.
On aura également besoin des fichiers de données suivantes disponible sur le serveur :
Ce TP 3/4 est sur deux séances, il fait suite au TP2. Le point de départ correspond à peu près au résultat attendu en fin de TP2, où on a une page d’affichage des nouvelles avec un filtre sur le mois et l’année souhaités.
L’objectif du TP 3/4 est de réorganiser le code pour pouvoir gérer un tableau de nouvelle obtenu de façon asynchrone, alors que dans le TP2 ces nouvelles étaient stockés dans un constante.
Globalement, il s’agit d’écrire un programme le plus fonctionnellement possible :
- avec un maximum d’initialisations
const
, sans utiliser d’affectation ou delet
(et sansvar
qui est de toute façon à bannir); - en utilisant les fonctions
map
,reduce
etfilter
des tableaux sans utiliser de bouclesfor
ouwhile
.
Exercice 0 : chargement de nouvelles
Cet exercice est une prise en main du code de départ.
- expliquer comment fonctionne la fonction
elimine_doublons_trie
est pourquoi elle est utile dans le code ; - expliquer ce que fait la fonction
mois_de_annee
; - expliquer ce que font les fonctions
maj_annees
etchange_annee
et quand elles sont utilisées ; - réécrire la fonction
liste_to_options
en style fonctionnel (dans le style demois_de_annee
par exemple) ; - modifier la fonction
maj_mois
pour la liste des mois ne contiennent que les mois de l’année sélectionnée en utilisantliste_to_options
etmois_de_annee
.
Exercice 1 : chargement dynamique des nouvelles : prise en main
Au lieu d’utiliser une liste de nouvelles statique, fournie directement dans le code JavaScript par la constante globale donnees_exemple
, on va vouloir charger dynamiquement les nouvelles depuis un fichier téléchargé par le navigateur.
La fonction fournie charge_donnees(url, callback)
charge, de façon asynchrone, le fichier json à l’adresse url
, puis appelle la fonction callback
en lui passant en argument le contenu du fichier chargé. Cette fonction utilise l’API fetch
vue en cours et pourra servir d’exemple pour les exercices suivants. Vous pouvez aussi vous servir directement de fetch
en utilisant les promesses, sans passer par charge_donnees
.
- Pour commencer, faire un appel à
charge_donnees(nouvellesUrl, console.log);
dans la console et observer le résultat. - Remplacer l’appel à
maj_annees(donnees_exemple)
dansinit_menus
pour charger les nouvelles contenues dans le fichiernouvelles.json
en utilisantcharge_donnees
. On utiliseramaj_annees
dans le callback passé en paramètre decharge_donnees
et on s’interdira de faire une affectation sur la constantedonnees_exemple
.
A ce stade, le téléchargement doit fonctionner mais l’affichage des nouvelles filtrées n’est pas encore correct. Ne passez pas à la suite si vous ne comprenez pas pourquoi.
Exercice 2 : Passage d’information via des fermetures : se passer de donnees_exemple
- Déplacer la constante
donnees_exemple
dans le fichierLIFAP5-TP3-test.js
. Sa valeur n’est plus disponible dansLIFAP5-TP3.js
: la page ne sera plus fonctionnelle et des erreurs JavaScript seront affichées dans la console. - Ajouter un argument dans toutes les fonctions qui utilisaient
donnees_exemple
pour les rendre paramétriques et corriger les appels à ces fonctions pour passer le paramètre. Suivez les erreurs de la console pour y parvenir.
À la fin de cet exercice, l’affichage des nouvelles doit être fonctionnel et utiliser les bonnes données chargées dynamiquement sans utiliser de variables globales.
Attention à la gestion des événements: il faut bien réfléchir à ce que l’on range dans les champs onchange
des menus : on y place une fonction qui n’attend pas d’arguments et qui sera empilée dans la task queue du navigateur. Comme cette fonction doit accéder aux nouvelles courantes, il faut utiliser une fermeture qui va capturer ces nouvelles.
Exercice 3 : Affichage du contenu des nouvelles
Pour le moment, on affiche juste la liste des titres des nouvelles. On souhaite en afficher aussi le contenu, mais seulement lorsque l’on clique sur le titre de la nouvelle. On souhaite également que l’affichage d’un contenu masque les autres contenus. Pour cela on propose de procéder de la manière suivante:
- Modifier
formate_titre
pour que chaque élément de la liste contiennent un élémentdiv
avec le contenu de la nouvelle. - Attribuer un attribut
id
unique à chacun de ces<div>
ainsi qu’à chacun des éléments<li>
qui le contient, cela permettra de les manipuler viadocument.getElementById
. - Utiliser la fonction
masque_affiche_contenus
pour réagir au clic sur un titre (i.e. faire une affectation sur le champonclick
de l’élément contenant le titre). Il faudra bien enregistrer une fonction pour chaque titre. - Cacher toutes les nouvelles en mettant par défaut le style
display
de tous les contenus à “none”.
Il est attendu d’arriver à cet exercice en fin de la première séance de TP. Si ce n’est pas le cas, le faire entre les deux séances.
Aide
- Le plus pratique pour générer les identifiants des
<li>
et des<div>
est de modifier la liste des une bonne foi pour toute en leur donnant comme identifiant leur position dans le tableau. - Dans les méthodes d’itération sur les listes (e.g.
map
), la fonction passée en argument peut prendre en plus de l’élément à traiter, un deuxième argument correspondant à son index dans le tableau. document.getElementById
peut être utilisé pour accéder à un élément en fonction de sonid
.- Plus pratique encore,
Document.querySelectorAll()
permet de retrouver tous les éléments qui sont sélectionné par un sélecteur CSS comme par exempledocument.querySelectorAll("#elt-nouvelles ul li > div")
. Question : que fait ce sélecteur ? - L’attribut
style
des éléments contient lui-même un champdisplay
qui représente la valeur du style CSSdisplay
. - Si vous manipulez le DOM avec
ParentNode.children
, attention, la colelction retournée est un pseudo tableau, qui n’a pas de méthodemap
: il faut le transformer avec par exempleArray.from()
Exercice 4 : l’annuaire de nouvelles
On ne peut pas faire cet exercice sans avoir terminé le précédent, vu qu’il s’agit d’une extension.
Le fichier annuaire.json
contient une structure JSON avec des liens vers des listes de nouvelles.
- Ajouter à la page un menu déroulant supplémentaire permettant de choisir la liste de nouvelles contenues dans
annuaire.json
. Un choix dans ce menu déclenchera le chargement des données correspondant à la bonne liste de nouvelles, comme dans les exercices précédents - Traiter les cas d’erreurs : quand on choisit un fichier de nouvelle inexistant, on doit avoir un message d’erreur qui s’affiche dans la page web et pas un arrêt brutal de l’exécution du JavaScript avec l’erreur lisible uniquement dans la console. Pour cela, utiliser la méthode
catch
des promesses
Afin d’afficher un message d’erreur à l’utilisateur en cas de problème de chargement, on utilisera directement l’API fetch
vue en cours au lieu de la fonction charge_donnees
.
Exercice 5 : soumission de données en POST
Jusqu’à présent, les requêtes fetch
n’ont été utilisées que pour lire des données sur le serveur, dans cet exercice on va en envoyer. Pour cela, on va utiliser un formulaire simple comme le suivant. Le but sera d’envoyer le contenu saisi au serveur (qu’on imagine ajouter l’utilisateur à une base de données) et de récupérer son résultat.
<form action="#" method="get" class="form-example">
<div class="form-example">
<label for="name">Nom : </label>
<input type="text" name="name" id="name" required />
</div>
<div class="form-example">
<label for="email">Email : </label>
<input type="email" name="email" id="email" />
</div>
<div class="form-example">
<label for="hard">Hard rock</label>
<input type="radio" name="category" value="hard" id="hard" />
</div>
<div class="form-example">
<label for="heavy">Heavy metal</label>
<input type="radio" name="category" value="heavy" id="heavy" />
</div>
<div class="form-example">
<label for="speed">Speed metal</label>
<input type="radio" name="category" value="speed" id="speed" />
</div>
<div class="form-example">
<label for="thrash">Thrash metal</label>
<input type="radio" name="category" value="thrash" id="thrash" />
</div>
<div class="form-example">
<input type="submit" value="Envoyer" />
</div>
</form>
- Ajouter le formulaire à la page. Tester le formulaire avec différente saisies. Regarder vers quelles URL vous êtes redirigés lors d’un clique sur Envoyer.
- Ajouter le handler suivant
form.onsubmit = function(ev) { console.debug(ev); console.debug(this); alert('onsubmit'); }
oùform
est le HTMLFormElement de votre formulaire. - Constater ce qui se passe quand on clique sur le bouton Envoyer. Ajouter l’instruction
ev.preventDefault();
dans le corps du handler et constater la différence. - Eventuellement, ajouter un autre
input
de votre choix au formulaire.
On veut maintenant récupérer les contenus saisis par l’utilisateur. Soit on parcourt le DOM pour regarder la propriété value
des (contreparties DOM des) éléments input
qui composent le formulaire, soit on passe par un objet FormData
. C’est la seconde solution qu’on retiend ici pour votre handler.
- Obtenir le contenu du formulaire avec
FormData(this)
dans votre handler. Vous pouvez parcourir son contenu ainsifor (const pair of formData.entries()) { ... }
oùpair
est un tableau de la forme[prop, value]
. Ainsi, générer un objet json de la forme{ nom , email, category }
à partir de l’objectFormData
. - Avec une requêtes
fetch
, envoyer cet objet dans le corps d’une requête HTTP POST comme ci-dessous. Récupérer la réponse HTTP envoyée par le serveur et vérifiez que le contenu du body est bien le même que celui envoyé. Utiliser unalert
pour indiquer si le résultat est conforme ou pas.
fetch('https://lifap5.univ-lyon1.fr/echo', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
,
}body: JSON.stringify(data),
; })