Sujet 6

  1. Bienvenue au 6e sujet
  2. Révisions
    1. Client / Serveur ?
    2. JS terminal
    3. Librairie
    4. import
    5. Durée lecture fichier
    6. readFile
    7. Après une promesse
    8. await
    9. Promesse: 1ere approche
    10. Nouveau projet node
    11. npm init fichier
    12. Node paquets
    13. Nombre paquets
    14. Répertoire
    15. Serveur Node.js
    16. Express / PHP: 1
    17. Express / PHP : 2
    18. Express / PHP: 3
    19. Route Express
  3. Profils: 2e partie :
    1. Est-ce que ça marche ?
    2. Click liste
    3. Click sur liste 2
    4. Correction
    5. Requête AJAX
    6. Correction
    7. Afficher le profil: fin
    8. Correction
  4. Profils: partie 3: la liste
    1. Route /profil
    2. Correction
    3. Parcourir un objet en JS
    4. Exercice : parcours valeurs
    5. Parcourir un objet
    6. Correction
    7. Liste: coté client
    8. Correction
  5. Profils: partie 4: editer
    1. Afficher le formulaire
    2. Correction
    3. Récupérer le id
    4. Correction
    5. Remplir le formulaire
    6. Correction
    7. enregistrer
    8. Correction
    9. Envoyer
    10. Route POST
    11. Correction
    12. Finition
    13. Correction

1. Bienvenue au 6e sujet

1.1 Bienvenue au 6e sujet

Pour votre progression, c'est très important de finir le sujet précédent avant de commencer celui-ci.

Vous devez travailler connectés (lien « Connexion » en haut à droite de la page). Sinon, vous rencontrerez des problèmes d'avancement et vous risquez d'être comptés absent. En étant connectés vous retrouverez l'endroit où vous en étiez à la séance suivante.

2. Révisions

2.1 Révisions

Commençons par une rapide révision des notions des sujets précédents.

Révisions

2.2 Client / Serveur ?

Où s’exécute le code suivant ?

Révisions

2.3 JS terminal

Quelle commande faut-il taper dans un terminal pour accéder à une console JS ?

Révisions

2.4 Librairie

A l'intérieur du code JS on veut accéder à une librairie. Quel est le premier mot de la ligne à utiliser ?

Révisions

2.5 import

import * as abcd from 'node:fs/promises';
await XYZ.readFile('exemple.txt');

Que faut-il écrire à la place de XYZ ?


Révisions

2.6 Durée lecture fichier

Lire un fichier est une opération:

Révisions

2.7 readFile

Quel est le type renvoyé par fs.readFile ici ?

import * as fs from 'node:fs/promises';
fs.readFile('exemple.txt')

Révisions

2.8 Après une promesse

Il y a deux manières de faire quelque-chose après qu'une promesse soit tenue:

  1. la première manière demande qu'on crée une fonction supplémentaire (généralement anonyme), qui sera appelée plus tard
  2. la deuxième (plus courte) utilise un mot clé 

Quel est le mot clé de la deuxième manière de faire ?

Révisions

2.9 await

 Habituellement on utilise:

let fichier=await fs.readFile('exemple.txt');

Mais on pourrait décomposer:

let promesse=fs.readFile('exemple.txt');
let fichier=XYZ;

Que faut-il écrire à la place de XYZ (tout en utilisant await) ?

Révisions

2.10 Promesse: 1ere approche


Reprenons: Il y a deux manières de faire quelque-chose après qu'une promesse soit tenue:

  1. la première manière demande qu'on crée une fonction supplémentaire (généralement anonyme), qui sera appelée plus tard
  2. la deuxième utilise await

Utilisez la première approche pour afficher dans la console le contenu du fichier exemple.txt
Rappel : fs.readFile('exemple.txt')

Indication: Utilisez une fonction fléchée, avec des accolades { ... }

Révisions

2.11 Nouveau projet node

Quelle commande faut-il taper pour démarrer un nouveau projet Node.js ?

Révisions

2.12 npm init fichier

Quel fichier est crée par la commande

npm init


Révisions

2.13 Node paquets

En Node.js, quelle commande faut-il taper dans le terminal pour pouvoir utiliser une librairie appelée abcd ?

Révisions

2.14 Nombre paquets

En général, combien de paquets sont installés par la commande suivante ?

npm install abcd


Révisions

2.15 Répertoire

Dans quel répertoire sont téléchargés les paquets installés avec

npm install abcd

Révisions

2.16 Serveur Node.js

Comment s’appelle le framework Node.js permettant de créer un serveur web qu'on a utilisé ?

Révisions

2.17 Express / PHP: 1

Quelles affirmations sont vraies ?

Révisions

2.18 Express / PHP : 2

Quelles affirmations sont vraies ?

Révisions

2.19 Express / PHP: 3

Quelles affirmations sont vraies ?

Révisions

2.20 Route Express

Quand l'utilisateur visite l'URL http://localhost:3000/hello
on voudrait qu'il voit "Bonjour" dans son navigateur.

Express se trouve dans « app ».

Que faut-il écrire ?

Utilisez une fonction fléchée, avec des accolades, et un deuxième paramètre appelé « reponse ».

3. Profils: 2e partie :

3.1 Profils: 2e partie :

Revenons à nos profils du sujet précédent !

Attention: il y a d'importants changements dans tous les fichiers. Il faut tous les reprendre...

Rappels pour recréer la structure, si vous avez perdu le répertoire « profils »:

Les fichiers

app.js

import express from 'express';
import * as fs from "node:fs/promises";

const app = express();

app.use(express.static('public'));

app.listen(3000, () => {
console.log('Notre app « profils » écoute sur le port 3000')
});

app.get('/profil/:id', async (requete, reponse) => {
console.log('Requete GET /profil/:id reçue');
let id=requete.params.id;
let profilsTexte=await fs.readFile('profils.json','utf8');
let profils=JSON.parse(profilsTexte);
reponse.send(profils[id]);
})

profils.json

{
"p132": {"id": "p132", "nom": "Minou", "sexe": "homme", "portrait": "https://moodle.iutv.univ-paris13.fr/img/bjs2/chat2.svg", "messages": 54, "description": "Miaou ? 🐁 🐭 ? ", "likes": 132 },
"p125": {"id": "p125", "nom": "L. Neige", "sexe": "femme", "portrait": "https://moodle.iutv.univ-paris13.fr/img/bjs2/reindeer.png", "messages": 172, "description": "Je suis un peu timide.", "likes": 232 },
"p241": {"id": "p241", "nom": "Anonyme", "sexe": "femme", "portrait": "https://moodle.iutv.univ-paris13.fr/img/bjs2/et-masque.png", "messages": 2, "description": "J'ai beaucoup voyagé. Je cherche quelqu'un qui accepte les différences.", "likes": 5 },
"p112": {"id": "p112", "nom": "Père N.", "sexe": "homme", "portrait": "https://moodle.iutv.univ-paris13.fr/img/bjs2/santa.png", "messages": 1300482832, "description": "J'aime la nature et les paysages nordiques. Frileuses s'abstenir. Voyage d'affaires en période de fêtes.", "likes": 5103003013 }
}

public/index.html

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8"/>
<title>Profils</title>
<script src="https://moodle.iutv.univ-paris13.fr/img/bjs2/bjs2-js-lib.js"></script>
<link rel="stylesheet" href="profils.css"/>
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>
<body>
<ul id="liste">
<li data-id="p125">L. Neige</li>
<li data-id="p112">Père N.</li>
<li data-id="p132">Minou</li>
<li data-id="p241">Anonyme</li>
</ul>
<div id="profil-principal" class="profil">
<img class="portrait" src="" alt=""/>
<p class="nom"></p>
<p class="stats"><span class="messages">? messages</span> <span class="likes">? likes</span></p>
<p class="description"></p>
<span id="editer">[Éditer]</span>
</div>

<div id="formulaire">
<div id="formulaire-boite">
<span id="fermer">x</span>
<div id="ligne-nom" ><label for="edit-nom" >Nom: </label><input id="edit-nom" type="text"/> </div>
<div id="ligne-sexe" ><label for="edit-sexe" >Sexe: </label><!--
--><select id="edit-sexe">
<option value="femme">Femme</option>
<option value="homme">Homme</option>
<option value="autre">Autre</option>
</select>
</div>
<div id="ligne-messages" ><label for="edit-portrait" >Portrait: </label><input id="edit-portrait" type="text"/></div>
<div id="ligne-messages" ><label for="edit-messages" >Messages: </label><input id="edit-messages" type="number"/></div>
<div id="ligne-likes" ><label for="edit-likes" >Likes: </label><input id="edit-likes" type="number"/></div>
<div id="ligne-description"><label for="edit-description">Description:</label><textarea id="edit-description"></textarea> </div>
<input id="enregistrer" type="button" value="Enregistrer"/>
</div>
</div>

<script type="module" src="profils.js"></script>
</body>
</html>

public/profils.css

/* Inspiré de https://freefrontend.com/css-profile-cards/ */

@import url("https://fonts.googleapis.com/css2?family=Baloo+Paaji+2:wght@400;500&display=swap");

body{
background: linear-gradient(45deg, rgba(255,255,255,1) 0%, rgba(240,180,149,1) 100%);
background-repeat: no-repeat;
font-family: sans;
color: #444;
background-attachment: fixed;
}

/***********************************/
/* liste **/
/***********************************/

#liste{
display: flex;
list-style-type: none ;
}

#liste li{
margin: 1em;
background-color: #222831;
color: white;
padding: .7em;
border-radius: .3em;
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.4);
cursor: pointer;
}

#liste li{
margin: 1em;
background-color: #222831;
color: white;
padding: .7em;
border-radius: .3em;
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.4);
cursor: pointer;
}

#liste li:hover{
background-color: #522831;
}

/***********************************/
/* Profil **/
/***********************************/

#profil-principal{
margin-left: 7em;
}

.profil {
position: relative;
background-color: #222831;
color: white;
width: 17em;
text-align: center;
border-radius: 5px;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4);
font-family: 'Baloo Paaji 2', cursive;
padding: .5em;
}

.profil .portrait {
border-radius: 50%;
width: 160px;
height: 160px;
border: 5px solid #272133;
margin-top: 20px;
background-color: white;
}

.profil .portrait.homme {
box-shadow: 0 10px 50px rgba(25, 110, 235, .8);
}

.profil .portrait.femme {
box-shadow: 0 10px 50px rgba(235, 25, 110, .6);
}

.profil .stats {
font-size: 1.2em;
margin-top: .5em;
}

.profil .stats span {
margin: 1em;
}

.profil .nom{
margin-top: 15px;
margin-bottom: .5em;
font-size: 1.5em;

}

#editer{
cursor: pointer;
cursor: pointer;
}

#editer:hover{
color: #aa0;
}

.profil[data-id="p132"]::before{
content: '🐁';
position: absolute;
top: 200px;
right: -100px;
animation: 1s 1 normal souris;
animation-delay: 4000ms;
opacity: 0;
}

@keyframes souris {
from {
right: -100px;
opacity: 1;
}
to {
right: 300px;
opacity: 1;
}
}

.profil[data-id="p132"]::after{
content: '';
border-radius: 50%;
background-color: #444;
width: 5px;
height: 5px;
position: absolute;
top: 106px;
left: 133px;
animation: 1s 1 normal oeilg;
animation-delay: 4000ms;
z-index: 2;
opacity: 0;
}

.profil[data-id="p132"] .nom::before{
content: '';
border-radius: 50%;
background-color: #444;
width: 5px;
height: 5px;
position: absolute;
top: 107px;
left: 154px;
animation: 1s 1 normal oeild;
animation-delay: 4000ms;
z-index: 2;
opacity: 0;
}

@keyframes oeilg {
from {
opacity: 1;
left: 133px;
}
to {
opacity: 1;
left: 126px;
}
}

@keyframes oeild {
from {
opacity: 1;
left: 154px;
}
to {
opacity: 1;
left: 147px;
}
}

.profil[data-id="p132"] .nom::after{
content: '';
width: 14px;
height: 8px;
border-right: 8px solid white;
border-left: 7px solid white;
position: absolute;
top: 104px;
left: 129px;
animation: 1s 1 normal blanc;
animation-delay: 4000ms;
opacity: 0;
}
@keyframes blanc {
from {opacity: 1;}
to {opacity: 1;}
}

/***********************************/
/* Formulaire **/
/***********************************/

#formulaire{
display: none;
position: fixed;
top: 0;
left: 0;
z-index: 1000;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,.5);
}

#formulaire-boite{
position: absolute;
top: 5em;
left: 5em;
width: 25em;
background: linear-gradient(45deg, rgba(255,255,255,1) 0%, rgba(240,180,149,1) 100%);
border-radius: 5px;
padding: 1em;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4);
}

#fermer{
position: absolute;
right: 0;
top: 0;
display: inline-block;
padding: .3em;
cursor: pointer;
}

#fermer:hover{
color: #aa0;
}

#formulaire-boite>div{
margin: .5em 0;
}

#formulaire label{
display: inline-block;
min-width: 7em;
vertical-align: top;
}

#formulaire-boite>div input,#formulaire textarea{
width: 240px;
box-sizing: content-box;
}

#formulaire textarea{
height: 7em;
}
public/profils.js
// vide pour l'instant


Profils: 2e partie :

3.2 Est-ce que ça marche ?

Au dernier sujet on avait écrit:

app.get('/profil/:id', async (requete, reponse) => {
console.log('Requete GET /profil/:id reçue');
let id=requete.params.id;
let profilsTexte=await fs.readFile('profils.json','utf8');
let profils=JSON.parse(profilsTexte);
reponse.send(profils[id]);
})

Ce code est une « route ». Il reçoit une requete du type http://localhost:3000/profil/p132 et renvoie une réponse JSON contenant le profil en question.
Pour ça, il lit le fichier profils.json qui contient tous les profils, et en choisit un.

On va vérifier qu ça marche toujours:

Lancez

$ node app.js

Et affichez: http://localhost:3000/
Vous devriez voir une page avec du CSS correct, comme ceci:

Ouvrez un autre onglet et regardez: http://localhost:3000/profil/p132
Vous devriez voir des données JSON sur le profil p132 « Minou »

Profils: 2e partie :

3.3 Click liste

Notre page n'affiche personne, c'est triste !

On voudrait afficher un profil quand l'utilisateur clique sur un nom.
Les noms sont dans une liste:

      <ul id="liste">
<li data-id="p125">L. Neige</li>
<li data-id="p112">Père N.</li>
<li data-id="p132">Minou</li>
<li data-id="p241">Anonyme</li>
</ul>

On a le choix entre deux solutions pour réagir quand l'utilisateur clique sur un nom, donc sur <li>:

  1. on peut faire des addEventListener sur chacun des <li> ... donc une boucle
  2. on peut faire un seul addEventListener sur le <ul> et utiliser le fait qu'un événement remonte. C'est à dire que si l'utilisateur clique sur le <li>, les fonctions enregistrées sur le <li> (il n'y en a pas) seront appelées. Puis celles sur le <ul> (c'est la notre!), puis celles sur le parent de <ul>, etc.

On va utiliser la deuxième solution. Elle va être utile plus tard.

Comment s'appelle le comportement où un événement remonte progressivement dans l'arbre DOM ?
(souvenirs du S2!)

Indice: un mot en anglais commençant par un « b »

Profils: 2e partie :

3.4 Click sur liste 2


(plus de détails)

Écrivez le code permettant d'afficher « click li » sur la console, uniquement quand l'utilisateur a cliqué sur un <li> (et pas sur le <ul>, par exemple entre deux noms).

Indice: utilisez event.target.nodeName pour savoir ce qui a été cliqué.

Ensuite on veut récupérer le « id » du nom cliqué:

Les <li> contiennent, dans le HTML, le id du profil:

      <ul id="liste">
<li data-id="p125">L. Neige</li>
<li data-id="p112">Père N.</li>
<li data-id="p132">Minou</li>
<li data-id="p241">Anonyme</li>
</ul>
« data-id » est un attribut HTML. Pour lire un attribut utilisez getAttribute
Mettez le id du <li> cliqué dans une variable « id » et affichez le dans la console.

Écrivez le code, vérifiez qu'il fonctionne dans votre navigateur, puis copiez-le ici pour que votre enseignant puisse le relire plus tard:

Profils: 2e partie :

3.5 Correction

const liste         =document.getElementById('liste');
...

liste.addEventListener('click',(event)=>{
if(event.target.nodeName!=='LI'){return;}
console.log('click li');
const id=event.target.getAttribute('data-id');
console.log(id);
});

Profils: 2e partie :

3.6 Requête AJAX

On veut écrire le code pour chercher le profil sur le serveur et l'afficher.
Comme on aura besoin d'utiliser ce code par ailleurs, on va créer une fonction séparée.

Créez une fonction appelée afficher_profil(id) qui prend en paramètre l'id d'un profil.
Appelez-là depuis votre gestionnaire d'événement click.

Dans afficher_profil, utilisez simple_fetch pour chercher le profil sur le serveur.
Affichez le profil dans la console.


Écrivez le code, vérifiez qu'il fonctionne dans votre navigateur, puis copiez-le ici pour que votre enseignant puisse le relire plus tard:

Profils: 2e partie :

3.7 Correction

// Afficher le profil quand l'utilisateur clique sur un nom
liste.addEventListener('click',(event)=>{
if(event.target.nodeName!=='LI'){return;}
const id=event.target.getAttribute('data-id');
afficher_profil(id);
});

async function afficher_profil(id){
const profil=await simple_fetch('profil/'+id);
console.log(profil);
}

Profils: 2e partie :

3.8 Afficher le profil: fin

Créez en début de fichier le raccourci suivant:

const principal     =document.getElementById('profil-principal');

Dans afficher_profil, affichez tous les champs.
En particulier, n'oubliez pas le portrait:
il faut à la fois indiquer src et le sexe, qui est utilisé comme une classe dans l'image

Écrivez le code, vérifiez qu'il fonctionne dans votre navigateur, puis copiez-le ici pour que votre enseignant puisse le relire plus tard:


Profils: 2e partie :

3.9 Correction

const principal     =document.getElementById('profil-principal');

// Afficher le profil quand l'utilisateur clique sur un nom
liste.addEventListener('click',(event)=>{
if(event.target.nodeName!=='LI'){return;}
const id=event.target.getAttribute('data-id');
afficher_profil(id);
});

async function afficher_profil(id){
const profil=await simple_fetch('profil/'+id);
principal.querySelector('.nom' ).textContent=profil.nom;
principal.querySelector('.portrait' ).src=profil.portrait;
principal.querySelector('.portrait' ).className='portrait '+profil.sexe;
principal.querySelector('.messages' ).textContent=profil.messages+' messages';
principal.querySelector('.likes' ).textContent=profil.likes+' likes';
principal.querySelector('.description').textContent=profil.description;
}

4. Profils: partie 3: la liste

4.1 Profils: partie 3: la liste

On arrive enfin à voir les profils ! Ouf.

La liste de noms en haut de la page est fixée dans le HTML.
On ne peut pas ajouter de profils ! C'est dommage.

Dans les pages suivantes, on va essayer de construire une liste dynamique.

Profils: partie 3: la liste

4.2 Route /profil

Effacez les <li> dans <ul id="liste">

Pour pouvoir afficher la liste des noms, il va falloir la chercher sur le serveur.

Créez dans app.js une nouvelle route, qui corresponde à http://localhost:3000/profil

Affichez   « Requete GET /profil reçue » dans la console du serveur.
Vérifiez que ça marche.

Écrivez le code, vérifiez qu'il fonctionne dans votre navigateur, puis copiez-le ici pour que votre enseignant puisse le relire plus tard:

Profils: partie 3: la liste

4.3 Correction

app.get('/profil', async (requete, reponse) => {
console.log('Requete GET /profil reçue');
});

Profils: partie 3: la liste

4.4 ¤ Parcourir un objet en JS

En JS, il y a de nombreuses (beaucoup trop) manières de parcourir un objet.

Prenons un exemple:

let exemple={
"Joe": 123,
"Leila": 543,
"Tom": 91,
};
On voudrait faire une boucle pour parcourir soit les clés ("Joe", "Leila", "Tom" ), soit les valeurs (123,543,91), soit les deux (clés et valeurs).

Les boucles for...in posent des problèmes (hasOwnProperty, Object.hasOwn...).

L'idée est de transformer l'objet en un tableau et utiliser une boucle for...of:

Parcourir les valeurs:

Object.values(...) renvoie un tableau JS (et non pas un objet) avec toutes les valeurs.
On peut alors parcourir ce tableau avec une boucle for...of:

for(const  valeur of Object.values(exemple)){ 
console.log(valeur);
}

Parcourir les clés et les valeurs :

Object.entries(...) renvoie un tableau JS (et non pas un objet) constitué de petits tableaux contenant 2 éléments :une clés et une valeurs:

Object.entries(exemple) renvoie:

[
["Joe", 123],
["Leila", 543],
["Tom", 91],
]

On peut alors parcourir ce tableau avec une boucle for...of:

for(const [cle,valeur] of Object.entries(exemple)){ 
console.log(cle,valeur);
}

Profils: partie 3: la liste

4.5 Exercice : parcours valeurs


let commande={
"Salade": 4.5,
"Désert": 3.2,
"plat": 6,
};
let total=0;
XYZ

Que faut-il écrire à la place de XYZ pour calculer le prix total de la commande ?
Utilisez une boucle for...of et Object.values


Profils: partie 3: la liste

4.6 Parcourir un objet

Lisez le fichier profils.json et transformez le JSON en objet.


On voudrait renvoyer au navigateur un objet qui ressemble à ceci:
{"p132":"Minou", "p125":"L. Neige","p241":"Anonyme", ... }

On va commencer par un objet vide:

  let noms={};

Ensuite, on va lui ajouter tous les id:nom.
Il faut donc faire une boucle pour parcourir les profils.
Utilisez Object.values et for...of pour construire la réponse et la renvoyer au navigateur (rappel).

Vérifiez que ça marche avec : http://localhost:3000/profil

Écrivez le code, vérifiez qu'il fonctionne dans votre navigateur, puis copiez-le ici pour que votre enseignant puisse le relire plus tard:


Profils: partie 3: la liste

4.7 Correction

app.get('/profil', async (requete, reponse) => {
console.log('Requete GET /profil reçue');
let profilsTexte=await fs.readFile('profils.json','utf8');
let profils=JSON.parse(profilsTexte);
let noms={};
for(let profil of Object.values(profils)){
noms[profil.id]=profil.nom;
}
reponse.send(noms);
})

Profils: partie 3: la liste

4.8 Liste: coté client

Attention (2023): dans certaines salles de l'IUT la version de Firefox ne permet pas d'utiliser await directement. Vous devez créer une fonction async, mettre votre code dedans et appeler cette fonction.

Maintenant il nous reste à faire la partie client (navigateur).

Vers le début de votre fichier, utilisez simple_fetch pour chercher la liste des utilisateurs sur le serveur.
Ceci nous renvoie un objet.
Utilisez Object.entries et for...of pour parcourir cet objet (rappel).
Dans la boucle, créez les <li>.
N'oubliez pas l'attribut data-id (setAttribute)

Rappel création élément:

Écrivez le code, vérifiez qu'il fonctionne dans votre navigateur, puis copiez-le ici pour que votre enseignant puisse le relire plus tard:

Profils: partie 3: la liste

4.9 Correction

const principal     =document.getElementById('profil-principal');

// Chercher les noms pour créer la liste en haut de la page
const noms=await simple_fetch('profil');
for(const [id,nom] of Object.entries(noms)){
const li=document.createElement('li');
li.setAttribute('data-id',id);
li.textContent=nom;
liste.append(li);
}

// Afficher le profil quand l'utilisateur clique sur un nom
liste.addEventListener('click',(event)=>{
if(event.target.nodeName!=='LI'){return;}
const id=event.target.getAttribute('data-id');
afficher_profil(id);
});

async function afficher_profil(id){
const profil=await simple_fetch('profil/'+id);
principal.querySelector('.nom' ).textContent=profil.nom;
principal.querySelector('.portrait' ).src=profil.portrait;
principal.querySelector('.portrait' ).className='portrait '+profil.sexe;
principal.querySelector('.messages' ).textContent=profil.messages+' messages';
principal.querySelector('.likes' ).textContent=profil.likes+' likes';
principal.querySelector('.description').textContent=profil.description;
}

5. Profils: partie 4: editer

5.1 Profils: partie 4: editer

On veut maintenant pouvoir éditer les profils.


Profils: partie 4: editer

5.2 Afficher le formulaire

Un petit formulaire est prévu dans le HTML, mais il est caché par défaut dans le CSS (display: none).

Quand l'utilisateur clique sur éditer, affichez le formulaire (display: block).

Écrivez le code, vérifiez qu'il fonctionne dans votre navigateur, puis copiez-le ici pour que votre enseignant puisse le relire plus tard:

Profils: partie 4: editer

5.3 Correction

const principal     =document.getElementById('profil-principal');
const formulaire =document.getElementById('formulaire');

// Chercher les noms pour créer la liste en haut de la page
const noms=await simple_fetch('profil');
for(const [id,nom] of Object.entries(noms)){
const li=document.createElement('li');
li.setAttribute('data-id',id);
li.textContent=nom;
liste.append(li);
}

// Afficher le profil quand l'utilisateur clique sur un nom
liste.addEventListener('click',(event)=>{
if(event.target.nodeName!=='LI'){return;}
const id=event.target.getAttribute('data-id');
afficher_profil(id);
});

async function afficher_profil(id){
const profil=await simple_fetch('profil/'+id);
principal.querySelector('.nom' ).textContent=profil.nom;
principal.querySelector('.portrait' ).src=profil.portrait;
principal.querySelector('.portrait' ).className='portrait '+profil.sexe;
principal.querySelector('.messages' ).textContent=profil.messages+' messages';
principal.querySelector('.likes' ).textContent=profil.likes+' likes';
principal.querySelector('.description').textContent=profil.description;
}

// Afficher et remplir le formulaire quand l'utilisateur clique sur editer
document.getElementById('editer').addEventListener('click',async ()=>{
formulaire.style.display='block';
});


Profils: partie 4: editer

5.4 Récupérer le id

On voudrait remplir le formulaire avec les données du profil.
On a donc besoin d'aller chercher le profil sur le serveur.
Mais pour ça, il nous faut le id du profil.

Dans afficher_profil, créez un attribut data-id (avec setAttribute) sur #profil-principal contenant le id.
De cette manière, dans éditer, on peut récupérer le id avec getAttribute.
Faites-le et affichez le id dans la console quand l'utilisateur clique sur « Editer ».


Écrivez le code, vérifiez qu'il fonctionne dans votre navigateur, puis copiez-le ici pour que votre enseignant puisse le relire plus tard:

Profils: partie 4: editer

5.5 Correction

const principal     =document.getElementById('profil-principal');
const formulaire =document.getElementById('formulaire');

// Chercher les noms pour créer la liste en haut de la page
const noms=await simple_fetch('profil');
for(const [id,nom] of Object.entries(noms)){
const li=document.createElement('li');
li.setAttribute('data-id',id);
li.textContent=nom;
liste.append(li);
}

// Afficher le profil quand l'utilisateur clique sur un nom
liste.addEventListener('click',(event)=>{
if(event.target.nodeName!=='LI'){return;}
const id=event.target.getAttribute('data-id');
afficher_profil(id);
});

async function afficher_profil(id){
const profil=await simple_fetch('profil/'+id);
principal.setAttribute('data-id',id);
principal.querySelector('.nom' ).textContent=profil.nom;
principal.querySelector('.portrait' ).src=profil.portrait;
principal.querySelector('.portrait' ).className='portrait '+profil.sexe;
principal.querySelector('.messages' ).textContent=profil.messages+' messages';
principal.querySelector('.likes' ).textContent=profil.likes+' likes';
principal.querySelector('.description').textContent=profil.description;
}

// Afficher et remplir le formulaire quand l'utilisateur clique sur editer
document.getElementById('editer').addEventListener('click',async ()=>{
formulaire.style.display='block';
const id=principal.getAttribute('data-id');
console.log(id);
});

Profils: partie 4: editer

5.6 Remplir le formulaire

Utilisez simple_fetch pour chercher le profil sur le serveur.
Remplissez les champs.
Ajoutez aussi un attribut data-id (avec setAttribute) sur le formulaire (on en aura besoin plus tard).

Écrivez le code, vérifiez qu'il fonctionne dans votre navigateur, puis copiez-le ici pour que votre enseignant puisse le relire plus tard:

Profils: partie 4: editer

5.7 Correction

const principal     =document.getElementById('profil-principal');
const formulaire =document.getElementById('formulaire');
const liste =document.getElementById('liste');

// Chercher les noms pour créer la liste en haut de la page
const noms=await simple_fetch('profil');
for(const [id,nom] of Object.entries(noms)){
const li=document.createElement('li');
li.setAttribute('data-id',id);
li.textContent=nom;
liste.append(li);
}

// Afficher le profil quand l'utilisateur clique sur un nom
liste.addEventListener('click',(event)=>{
if(event.target.nodeName!=='LI'){return;}
const id=event.target.getAttribute('data-id');
afficher_profil(id);
});

async function afficher_profil(id){
const profil=await simple_fetch('profil/'+id);
principal.setAttribute('data-id',id);
principal.querySelector('.nom' ).textContent=profil.nom;
principal.querySelector('.portrait' ).src=profil.portrait;
principal.querySelector('.portrait' ).className='portrait '+profil.sexe;
principal.querySelector('.messages' ).textContent=profil.messages+' messages';
principal.querySelector('.likes' ).textContent=profil.likes+' likes';
principal.querySelector('.description').textContent=profil.description;
}

// Afficher et remplir le formulaire quand l'utilisateur clique sur editer
document.getElementById('editer').addEventListener('click',async ()=>{
const id=principal.getAttribute('data-id');
const profil=await simple_fetch('profil/'+id);
formulaire.style.display='block';
formulaire.setAttribute('data-id',id);
document.getElementById('edit-nom' ).value=profil.nom;
document.getElementById('edit-sexe' ).value=profil.sexe;
document.getElementById('edit-portrait' ).value=profil.portrait;
document.getElementById('edit-messages' ).value=profil.messages;
document.getElementById('edit-likes' ).value=profil.likes;
document.getElementById('edit-description').value=profil.description;

});

Profils: partie 4: editer

5.8 enregistrer

Quand l'utilisateur clique sur enregistrer, créez un objet « profil » avec tous les champs, y compris id.
Vous pouvez récupérer le id avec l'attribut data-id sur #formulaire

Affichez l'objet « profil » dans la console.

Écrivez le code, vérifiez qu'il fonctionne dans votre navigateur, puis copiez-le ici pour que votre enseignant puisse le relire plus tard:

Profils: partie 4: editer

5.9 Correction

document.getElementById('enregistrer').addEventListener('click',async ()=>{
const id=formulaire.getAttribute('data-id');
const profil={id};
profil.nom =document.getElementById('edit-nom' ).value;
profil.sexe =document.getElementById('edit-sexe' ).value;
profil.portrait =document.getElementById('edit-portrait' ).value;
profil.messages =document.getElementById('edit-messages' ).value;
profil.likes =document.getElementById('edit-likes' ).value;
profil.description=document.getElementById('edit-description').value;
console.log(profil);
});

Profils: partie 4: editer

5.10 Envoyer

On veut maintenant envoyer ce profil sur le serveur, pour qu'il l'enregistre dans profils.json

Quelle méthode faut-il utiliser pour la requête AJAX ?

Profils: partie 4: editer

5.11 Route POST

Coté serveur (app.js)

En vous inspirant de la route GET /profil/:id
Créez une route POST /profil/:id

Pour pouvoir lire les données envoyées par POST, ajoutez cette ligne dans app.js:

const app = express();

app.use(express.static('public'));
app.use(express.urlencoded({extended:false}));

Dans votre route POST, vous pouvez lire les données envoyées avec requete.body
Affichez les dans la console du serveur.
Ensuite, lisez profils.json, convertissez le en objet et modifiez le profil posté.
Retransformez-le en JSON et enregistrez le fichier avec

  await fs.writeFile('profils.json',JSON.stringify(profils));


Coté client (profils.js)

Utilisez simple_fetch(..., {post:profil})  pour envoyer la requête POST.
Une fois la requête finie, cachez le formulaire et affichez le profil avec la fonction afficher_profil()


Écrivez le code, vérifiez qu'il fonctionne dans votre navigateur, puis copiez-le ici pour que votre enseignant puisse le relire plus tard:

Profils: partie 4: editer

5.12 Correction

app.js
import express from 'express';
import * as fs from "node:fs/promises";

const app = express();

app.use(express.static('public'));
app.use(express.urlencoded({extended:false}));

app.listen(3000, () => {
console.log('Notre app « profils » écoute sur le port 3000')
});

app.get('/profil', async (requete, reponse) => {
console.log('Requete GET /profil reçue');
let profilsTexte=await fs.readFile('profils.json','utf8');
let profils=JSON.parse(profilsTexte);
let noms={};
for(let profil of Object.values(profils)){
noms[profil.id]=profil.nom;
}
reponse.send(noms);
})

app.get('/profil/:id', async (requete, reponse) => {
console.log('Requete GET /profil/:id reçue');
let id=requete.params.id;
let profilsTexte=await fs.readFile('profils.json','utf8');
let profils=JSON.parse(profilsTexte);
reponse.send(profils[id]);
})

app.post('/profil/:id', async (requete, reponse) => {
console.log('Requete POST /profil/:id reçue');
let id=requete.params.id;
let profilsTexte=await fs.readFile('profils.json','utf8');
let profils=JSON.parse(profilsTexte);
// Attention: ici on ne fait aucune validation.
// Il faudrait valider les données (nottament "portrait" et les nombres) et vérifier les noms des champs.
profils[id]=requete.body;
await fs.writeFile('profils.json',JSON.stringify(profils));
reponse.send({message:'ok'});
})

profils.js
const principal     =document.getElementById('profil-principal');
const formulaire =document.getElementById('formulaire');
const liste =document.getElementById('liste');


// Chercher les noms pour créer la liste en haut de la page
const noms=await simple_fetch('profil');
for(const [id,nom] of Object.entries(noms)){
const li=document.createElement('li');
li.setAttribute('data-id',id);
li.textContent=nom;
liste.append(li);
}

// Afficher le profil quand l'utilisateur clique sur un nom
liste.addEventListener('click',(event)=>{
if(event.target.nodeName!=='LI'){return;}
const id=event.target.getAttribute('data-id');
afficher_profil(id);
});

async function afficher_profil(id){
const profil=await simple_fetch('profil/'+id);
principal.setAttribute('data-id',id);
principal.querySelector('.nom' ).textContent=profil.nom;
principal.querySelector('.portrait' ).src=profil.portrait;
principal.querySelector('.portrait' ).className='portrait '+profil.sexe;
principal.querySelector('.messages' ).textContent=profil.messages+' messages';
principal.querySelector('.likes' ).textContent=profil.likes+' likes';
principal.querySelector('.description').textContent=profil.description;
}

// Afficher et remplir le formulaire quand l'utilisateur clique sur editer
document.getElementById('editer').addEventListener('click',async ()=>{
const id=principal.getAttribute('data-id');
const profil=await simple_fetch('profil/'+id);
formulaire.style.display='block';
formulaire.setAttribute('data-id',id);
document.getElementById('edit-nom' ).value=profil.nom;
document.getElementById('edit-sexe' ).value=profil.sexe;
document.getElementById('edit-portrait' ).value=profil.portrait;
document.getElementById('edit-messages' ).value=profil.messages;
document.getElementById('edit-likes' ).value=profil.likes;
document.getElementById('edit-description').value=profil.description;
});

// Envoyer le profil edit par POST au serveur quand l'utilisateur clique sur enregistrer
document.getElementById('enregistrer').addEventListener('click',async ()=>{
const id=formulaire.getAttribute('data-id');
const profil={id};
profil.nom =document.getElementById('edit-nom' ).value;
profil.sexe =document.getElementById('edit-sexe' ).value;
profil.portrait =document.getElementById('edit-portrait' ).value;
profil.messages =document.getElementById('edit-messages' ).value;
profil.likes =document.getElementById('edit-likes' ).value;
profil.description=document.getElementById('edit-description').value;
await simple_fetch('profil/'+id,{post:profil});
formulaire.style.display='none';
afficher_profil(id);
});

Profils: partie 4: editer

5.13 Finition

On y est presque!

Quelques petites touches:

Écrivez le code, vérifiez qu'il fonctionne dans votre navigateur, puis copiez-le ici pour que votre enseignant puisse le relire plus tard:

Profils: partie 4: editer

5.14 Correction

const principal     =document.getElementById('profil-principal');
const liste =document.getElementById('liste');
const formulaire =document.getElementById('formulaire');

// Chercher les noms pour créer la liste en haut de la page
const noms=await simple_fetch('profil');
for(const [id,nom] of Object.entries(noms)){
const li=document.createElement('li');
li.setAttribute('data-id',id);
li.textContent=nom;
liste.append(li);
}

// Afficher le profil quand l'utilisateur clique sur un nom
liste.addEventListener('click',(event)=>{
if(event.target.nodeName!=='LI'){return;}
const id=event.target.getAttribute('data-id');
afficher_profil(id);
});

async function afficher_profil(id){
principal.style.display='block';
const profil=await simple_fetch('profil/'+id);
principal.setAttribute('data-id',id);
principal.querySelector('.nom' ).textContent=profil.nom;
principal.querySelector('.portrait' ).src=profil.portrait;
principal.querySelector('.portrait' ).className='portrait '+profil.sexe;
principal.querySelector('.messages' ).textContent=profil.messages+' messages';
principal.querySelector('.likes' ).textContent=profil.likes+' likes';
principal.querySelector('.description').textContent=profil.description;
}

// Afficher et remplir le formulaire quand l'utilisateur clique sur editer
document.getElementById('editer').addEventListener('click',async ()=>{
const id=principal.getAttribute('data-id');
const profil=await simple_fetch('profil/'+id);
formulaire.style.display='block';
formulaire.setAttribute('data-id',id);
document.getElementById('edit-nom' ).value=profil.nom;
document.getElementById('edit-sexe' ).value=profil.sexe;
document.getElementById('edit-portrait' ).value=profil.portrait;
document.getElementById('edit-messages' ).value=profil.messages;
document.getElementById('edit-likes' ).value=profil.likes;
document.getElementById('edit-description').value=profil.description;
});

// Envoyer le profil edit par POST au serveur quand l'utilisateur clique sur enregistrer
document.getElementById('enregistrer').addEventListener('click',async ()=>{
const id=formulaire.getAttribute('data-id');
const profil={id};
profil.nom =document.getElementById('edit-nom' ).value;
profil.sexe =document.getElementById('edit-sexe' ).value;
profil.portrait =document.getElementById('edit-portrait' ).value;
profil.messages =document.getElementById('edit-messages' ).value;
profil.likes =document.getElementById('edit-likes' ).value;
profil.description=document.getElementById('edit-description').value;
await simple_fetch('profil/'+id,{post:profil});
formulaire.style.display='none';
afficher_profil(id);
});

document.getElementById('fermer').addEventListener('click',()=>{
formulaire.style.display='none';
});

Dans profils.css:

#profil-principal{
    margin-left: 7em;
    display: none;
}