TP - page de profil¶
Grâce à la page de création de comptes que nous venons de réaliser, un utilisateur peut aisément créer un nouveau compte. Cependant, les coordonnées qu’il entre ne peuvent, pour l’instant, pas être changées. De plus, les autres utilisateurs du forum ne peuvent, eux non plus, pas accéder à ses coordonnées.
C’est pourquoi une page de profil s’avère indispensable. Vous allez donc l’élaborer au travers d’un travail pratique. Voici les résultat que vous obtiendrez :

Lorsque la largeur de l’écran devient trop petite pour afficher tout le texte sur une même ligne, l’avatar change de position. Il s’affiche alors au-dessus des informations sur l’utilisateur :

La page de profil est accessible, lorsqu’un utilisateur est connecté, par un clic sur son nom d’utilisateur :

Si un utilisateur connecté visite son profil, il aura accès à la modification de ses coordonnées qui remplaceront le bouton “envoyer un courriel” :

Si l’utilisateur clique sur les boutons coordonnées
ou mot de passe
, des formulaires pour modifier ces données s’affichent. Pour les coordonnées :

Pour le mot de passe :

Lorsque la fenêtre est plus petite, ces formulaires se comportent à la manière de celui de la page nouveau_compte
.
En cliquant sur le bouton enregistrer
, les données, si elles sont validées (de la même manière que sur le formulaire de nouveau_compte
), sont mises à jour dans la base de données et modifiées directement sur la page web (aucune actualisation n’est nécessaire). S’affiche alors un message de confirmation :

Dans le cas de la modification du mot de passe, il faut vérifier si l’ancien mot de passe correspond bien à celui qui se trouve dans la base de données. Si c’est le cas et que le nouveau mot de passe est validé, le mot de passe est modifié dans la base de données et un message de confirmation apparaît :

Lors de la création du compte d’utilisateur, l’application avait attribué à la colonne avatar
de la table users
de la base de données un nombre au hasard entre 1 et 100. Ce nombre correspond au numéro de l’avatar qui est attribué à l’utilisateur. Si l’utilisateur souhaite changer son avatar, il peut donc en choisir un autre parmi 100 choix en cliquant sur le bouton avatar
:

Un clic sur une image change l’avatar. Cette modification est enregistrée dans la base de données et apparaît directement en grand à la gauche des informations sur le profil.
Pistes¶
- Les formulaires pour modifier les coodronnées et le mot de passe peuvent être directement repris de la page de création de compte que nous avons précédemment créé. De même, la vérification de ce formulaire peut être faite avec le fichier
./static/js/verification_form.js
- Tous les avatars sont disponibles à partir de ce lien. Dans ce dossier compressé, vous trouverez deux dossiers,
avatars_grands
etavatars_petits
. Il est conseillé d’utiliseravatars_petits
pour le choix les avatars, afin de minimiser le temps de chargement de la page
Correction¶
Code côté serveur¶
Nous créons tout d’abord une nouvelle route dans routes.js
:
app.get(/^\/profil_([a-z0-9]{4,10})/i, function(req, res) {
sql.profil(mysql, sql, req.params[0], function(result) {
res.render('profil.ejs', {
nom_utilisateur: session.session_active(req.session, req),
profil : result
});
});
});
Note
Ici, l’adresse de la page ressemblera à http://domaine.com/profil_nomdutilisateur
. Pour récupérer le nom d’utilisateur sans utiliser d’expression régulière, il est aussi possible d’entrer l’instruction suivante :
app.get('/profil/:nom_utilisateur', function(req, res) {
var nom_utilisateur = req.params.nom_utilisateur;
// ...
});
Cependant, un problème surgirait avec les liens qui se trouvent dans cette page (par exemple, ceux de la barre de navigation). En cliquant sur le bouton accueil
, l’utilisateur serait redirigé vers http://domaine.com/profil/
, qui ne correspond pas à la page d’accueil. Il faudrait alors insérer dans toutes les balises <a>
de cette page l’adresse web complète des liens, ce qui génère une difficulté dans le cas d’un changement de nom de domaine.
Ensuite, nous ajoutons quelques événements socket
dans socket.js
:
// modifie les coordonnées
socket.on('modifier_compte', function(compte) {
var requete_sql = '\
UPDATE users SET email = ??, nom = ??, prenom = ?? WHERE nom_utilisateur = ??';
var inserts = [
compte.mail,
compte.nom,
compte.prenom,
compte.utilisateur
];
requete_sql = sql.preparer(mysql, requete_sql, inserts);
sql.requete(mysql, sql, requete_sql, function(results) {
socket.emit('mod_compte_ok', {
mail : compte.mail,
nom : compte.nom,
prenom : compte.prenom,
utilisateur : compte.utilisateur,
broadcast : false
});
socket.broadcast.emit('mod_compte_ok', {
mail : compte.mail,
nom : compte.nom,
prenom : compte.prenom,
utilisateur : compte.utilisateur,
broadcast : true
});
});
});
// modifie le mot de passe
socket.on('modifier_password', function(mod) {
var requete_sql = '\
SELECT mot_de_passe \
FROM users \
WHERE nom_utilisateur = ??';
var inserts = [mod.nom_utilisateur];
requete_sql = sql.preparer(mysql, requete_sql, inserts);
sql.requete(mysql, sql, requete_sql, function(results) {
if (mod.ancien_password == results[0].mot_de_passe) {
var requete_sql = '\
UPDATE users \
SET mot_de_passe = ?? \
WHERE nom_utilisateur = ??';
var inserts = [mod.nouveau_password, mod.nom_utilisateur];
requete_sql = sql.preparer(mysql, requete_sql, inserts);
sql.requete(mysql, sql, requete_sql);
socket.emit('mod_password_ok');
} else {
socket.emit('ancien_password_incorrect');
}
});
});
// change l'avatar d'un utilisateur
socket.on('changement_avatar', function(d) {
var requete_sql = 'UPDATE users SET avatar = ? WHERE nom_utilisateur = ??';
var inserts = [d.avatar, d.nom_utilisateur];
requete_sql = sql.preparer(mysql, requete_sql, inserts);
sql.requete(mysql, sql, requete_sql);
});
Ces instructions permettent d’enregistrer les modifications de l’utilisateur dans la base de données. De plus, le mot de passe est vérifié, et s’il ne correspond pas à celui de la base de données, l’événement ancien_password_incorrect
est émis.
Les événements mod_compte_ok
et mod_password_ok
permettentt, quant à eux, d’afficher les alertes informant l’utilisateur que ses coordonnées ont bien été modifiées.
Enfin, implémentons la fonction profil()
dans le fichier sql.js
:
// retourne toutes les informations pour afficher un profil
exports.profil = function(mysql, sql, utilisateur, callback) {
var requete_sql = 'SELECT * FROM users WHERE nom_utilisateur = ??';
var inserts = [utilisateur];
requete_sql = sql.preparer(mysql, requete_sql, inserts);
sql.requete(mysql, sql, requete_sql, function(results) {
callback(results[0]);
});
}
Code côté client¶
HTML et EJS¶
Commençons par le fichier EJS principal, ./views/profil.ejs
:
<!DOCTYPE html>
<html lang="fr">
<% if (!profil) { %>
<script>
// si le profil n'existe pas, l'utilisateur est redirigé vers la page d'erreur
window.location.assign('error');
</script>
<% } else { %>
<head>
<% include balise_head %>
<script src="static/js/verification_form.js"></script>
<script src="static/js/profil.js"></script>
<script> // ces fonctions javascript ont besoin de variables EJS
function inserer_form() {
byId('nc_prenom').value = '<%= profil.prenom %>';
byId('nc_nom').value = '<%= profil.nom %>';
byId('nc_prenom_mobile').value = '<%= profil.prenom %>';
byId('nc_nom_mobile').value = '<%= profil.nom %>';
byId('nc_mail').value = '<%= profil.email %>';
}
</script>
</head>
<body>
<% include barre_navigation %>
<div class="container">
<div class="row">
<div class="col-lg-offset-2 col-md-offset-1 col-md-10 col-lg-8
blanc corps_page">
<div class="alert alert-success"
style="text-align:center; display:none"
id="coordonnees_success">
Les coordonnées ont été modifiées avec succès
</div>
<div class="alert alert-success"
style="text-align:center; display:none"
id="password_success">
Le mot de passe a été modifié avec succès
</div>
<h4 class="gros_titre">Profil</h4>
<div class="avatar_profil_mobile">
<img src="static/avatars_grands/<%= profil.avatar %>.jpg"
class="avatar_profil">
</div>
<div class="row">
<div class="col-xs-4 avatar_profil_desktop">
<img src="static/avatars_grands/<%= profil.avatar %>.jpg"
class="avatar_profil"><br>
</div>
<div class="col-xs-8" id="infos_profil">
<p style="font-size:16px; margin-top:15px;">
<span id="info_nom_utilisateur">
Nom d'utilisateur : <%= profil.nom_utilisateur %>
</span><br>
<span id="info_prenom">
Prénom : <%= profil.prenom %>
</span><br>
<span id="info_nom">
Nom : <%= profil.nom %>
</span><br>
<span id="info_email">
E-mail : <%= profil.email %>
</span><br>
</p>
<% if (nom_utilisateur === profil.nom_utilisateur) { %>
<span class="btn btn-default" onclick="afficher_mod();">
<span class="glyphicon glyphicon-pencil"></span>
Coordonnées
</span>
<span class="btn btn-default" onclick="afficher_mod_password();">
<span class="glyphicon glyphicon-pencil"></span>
Mot de passe
</span>
<span class="btn btn-default" onclick="afficher_mod_avatar();">
<span class="glyphicon glyphicon-pencil"></span>
Avatar
</span>
<% } else { %>
<a class="btn btn-primary" href="mailto:<%= profil.email %>">
<span class="glyphicon glyphicon-pencil"></span>
Envoyer un courriel
</a>
<% } %>
</div>
</div>
<% include form_profil %>
<div id="mod_avatar" style="display:none;">
<script> avatars_small('<%= nom_utilisateur %>'); </script>
</div>
</div>
<div class="col-lg-offset-2 col-md-offset-1"></div>
</div>
</div>
<script>
// ces fonctions doivent attendre que la page soit chargée pour être exécutées
responsive();
inserer_form();
verifier_form();
</script>
</body>
<% } %>
<html>
Note
Ce code contient quelques balises <script>
, qui sont normalement à placer dans les fichiers statiques qui n’ont que du javascript
. Cependant, dans ce cas, les balises concernées ont besoin de variables EJS
qui ne sont accessibles que depuis cette page. De plus, les fonctions responsive()
, inserer_form()
et verifier_form()
sont disposées à la fin du code pour être exécutées une fois toute la page chargée.
Afin de mieux structurer le code, le formulaire de modification de coordonnées et de mot de passe se trouve dans un autre fichier, form_profil.ejs
.
Astuce
Afin d’éviter certaines redondances, il est possible de reprendre le fichier form_nouveau_compte
, presque identique à form_profil
, mais il faut ensuite exécuter des commandes javascript
pour masquer certains champs (comme le nom d'utilisateur
) et n’afficher qu’une partie du formulaire (le formulaire de modification de coordonnées est séparé de celui du mot de passe).
<form class="form-horizontal" id="mod_coordonnees">
<div class="form-group">
<h4 class="gros_titre">Modifier les coordonnées</h4>
</div>
<div class="row desktop">
<div class="form-group">
<div id="form_nc_prenom">
<label for="nc_prenom" class="col-sm-3 control-label">Prénom</label>
<div class="col-lg-4 col-sm-4">
<input type="text" class="form-control" id="nc_prenom"
onkeyup="verifier('prenom');">
<span class="help-block message_erreur" id="prenom_incorrect">
Ce prénom est invalide.
</span>
</div>
</div>
<div id="form_nc_nom">
<label for="nc_nom" class="col-sm-1 control-label">Nom</label>
<div class="col-lg-4 col-sm-4">
<input type="text" class="form-control" id="nc_nom"
onkeyup="verifier('nom');">
<span class="help-block message_erreur" id="nom_incorrect">
Ce nom est invalide.
</span>
</div>
</div>
</div>
</div>
<div class="row mobile">
<div class="form-group" id="form_nc_prenom_mobile">
<label for="nc_prenom_mobile" class="col-sm-4 control-label">Prénom</label>
<div class="col-lg-8 col-sm-8">
<input type="text" class="form-control" id="nc_prenom_mobile"
onkeyup="verifier('prenom_mobile');">
<span class="help-block message_erreur" id="prenom_mobile_incorrect">
Ce prénom est invalide.
</span>
</div>
</div>
</div>
<div class="row mobile">
<div class="form-group" id="form_nc_nom_mobile">
<label for="nc_nom_mobile" class="col-sm-4 control-label">Nom</label>
<div class="col-lg-8 col-sm-8">
<input type="text" class="form-control" id="nc_nom_mobile"
onkeyup="verifier('nom_mobile');">
<span class="help-block message_erreur" id="nom_mobile_incorrect">
Ce nom est invalide.
</span>
</div>
</div>
</div>
<div class="row">
<div class="form-group" id="form_nc_mail">
<label for="nc_mail" class="col-lg-2 col-sm-2 control-label">Mail</label>
<div class="col-lg-10 col-sm-10">
<input type="text" class="form-control" id="nc_mail"
onkeyup="verifier('mail');">
<span class="help-block message_erreur" id="mail_incorrect">
Cette adresse e-mail est invalide.
</span>
</div>
</div>
</div>
<div class="form-group">
<span class="pull-right btn btn-primary btn-nouveau_compte"
onclick="modifier_compte('<%= profil.nom_utilisateur %>');">
Enregistrer
</span>
</div>
</form>
<form class="form-horizontal" id="mod_password">
<div class="form-group">
<h4 class="gros_titre">Modifier le mot de passe</h4>
</div>
<div class="row">
<div class="form-group" id="form_nc_ancien_password">
<label for="nc_password" class="col-lg-3 col-sm-4 control-label">
Ancien mot de passe
</label>
<div class="col-lg-9 col-sm-8">
<input type="password" class="form-control" id="nc_ancien_password">
<span class="help-block message_erreur" id="ancien_password_incorrect">
Le mot de passe est incorrect.
</span>
</div>
</div>
</div>
<div class="row">
<div class="form-group" id="form_nc_nouveau_password">
<label for="nc_password" class="col-lg-3 col-sm-4 control-label">
Nouveau mot de passe
</label>
<div class="col-lg-9 col-sm-8">
<input type="password" class="form-control" id="nc_nouveau_password"
onkeyup="verifier_nouveau_password();">
<span class="help-block message_erreur" id="nouveau_password_incorrect">
Le mot de passe doit contenir au minimum 4 caractères, au maximum
20 caractères, et peut être uniquement composé de chiffres et de lettres.
</span>
</div>
</div>
</div>
<div class="form-group">
<span class="pull-right btn btn-primary btn-nouveau_compte"
onclick="modifier_password('<%= profil.nom_utilisateur %>');">
Enregistrer
</span>
</div>
</form>
Important
Dans ce code, vous constatez que des variables EJS
(en l’occurence, il s’agit du nom d’utilisateur du profil visité) sont directement injectées dans les événements onclick
des boutons. Le nom d’utilisateur est ensuite interprété et inséré dans une requête SQL
pour mettre à jour les coordonnées de l’utilisateur. Si ce système est utilisé pour faciliter l’accès au nom d’utilisateur, éviter des allez-retour entre le serveur et la base de données et simplifier la compréhension du code de l’application, il a cependant une faille de sécurité.
En effet, n’importe quel utilisateur pourrait modifier les coordonnées d’un autre utilisateur en envoyant un événement socket.io
depuis la console de son navigateur.
Feuilles de style¶
Le design de cette page nécéssite quelques directives css
supplémentaire :
.btn-nouveau_compte {
margin-bottom:4px
margin-top:-15px;
position: relative;
bottom: 9px;
}
#mod_coordonnees, #mod_password {
padding-right:20px;
display:none;
}
Il est également souhaitable de parfaire le responsive design
de cette page, et en particulier le comportement de l’avatar sur des appareils de petite taille :
.avatar_profil_mobile {
display: none;
}
@media(max-width: 572px) {
.avatar_profil {
max-width: 110%;
vertical-align: middle;
}
}
@media(min-width: 570px) {
#mod_avatar {
position: relative;
bottom: 25px;
}
}
@media(max-width: 500px) {
.avatar_profil {
position: relative;
top: 5px;
}
}
@media(max-width: 400px) {
.avatar_profil_desktop {
display: none;
}
.avatar_profil_mobile {
display: block;
position: relative;
bottom: 35px;
text-align: center;
}
}
Ici, des media queries
spécifient un léger mouvement vers le bas de l’avatar lorsque la fenêtre se rétrécit. La dernière, quant à elle, spécifie simplement l’affichage de l’avatar au-dessus des données de l’utilisateur lorsque la largeur de la fenêtre est inférieure à 400 px
.
Javascript¶
Comme nous réutilisons le même formulaire que pour la création d’un compte, nous n’avons pas besoin de réécrire toute la gestion du formulaire interactif. Il suffit d’ajouter un case
à la fonction get_regex()
de ./static/js/verification_form.js
pour gérer l’expression régulière avec le champ Nouveau mot de passe
:
function get_regex(data) {
switch(data) {
case 'utilisateur':
return /^[a-z0-9]{4,10}$/i;
// nous ajoutons "case : 'nouveau_password'"
case 'password': case 'nouveau_password':
return /^.{4,20}$/i;
case 'prenom': case 'prenom_mobile': case 'nom': case 'nom_mobile':
return /^[a-zâäàéèùêëîïôöçñ\ ]{2,50}$/i;
case 'mail':
return /^[a-z0-9\.-_]+\@[a-z0-9\.-_]+\.[a-z]{2,10}$/i;
}
}
Ensuite, nous employons la fonction verifier()
de ce fichier dans la page de profil pour vérifier les données entrées par l’utilisateur.
Restent alors la gestion des événements socket.io
et les diverses fonctions pour afficher les formulaires lorsque l’utilisateur clique sur les boutons coordonnées
, mot de passe
ou avatar
.
Nous allons créer un nouveau fichier, profil.js
, qui s’en occupera :
function responsive() {
if (window.matchMedia("(max-width: 400px)").matches) {
byId('infos_profil').className = 'col-xs-12';
}
else {
byId('infos_profil').className = 'col-xs-8';
}
if (!window.matchMedia("(min-width: 399px)").matches) {
byId('infos_profil').className = 'col-xs-12';
}
else {
byId('infos_profil').className = 'col-xs-8';
}
}
// vérifie le formulaire de modification de données
// entoure les champs correspondants en vert
function verifier_form() {
verifier('prenom');
verifier('nom');
verifier('mail');
}
// affiche le formulaire de modification des coordonnées
function afficher_mod() {
byId('mod_password').style.display = 'none';
byId('mod_avatar').style.display = 'none';
if (byId('mod_coordonnees').style.display === 'none') {
byId('mod_coordonnees').style.display = 'block';
}
else {
byId('mod_coordonnees').style.display = 'none';
}
}
// affiche le formulaire de modification du mot de passe
function afficher_mod_password() {
byId('mod_coordonnees').style.display = 'none';
byId('mod_avatar').style.display = 'none';
if (byId('mod_password').style.display === 'none') {
byId('mod_password').style.display = 'block';
}
else {
byId('mod_password').style.display = 'none';
}
}
// affiche le choix d'avatars
function afficher_mod_avatar() {
byId('mod_password').style.display = 'none';
byId('mod_coordonnees').style.display = 'none';
if (byId('mod_avatar').style.display === 'none') {
byId('mod_avatar').style.display = 'block';
}
else {
byId('mod_avatar').style.display = 'none';
}
}
// envoie un événement socket pour modifier les coordonnées
function modifier_compte(utilisateur) {
if (verifier('prenom') &&
verifier('nom') &&
verifier('mail'))
{
socket.emit('modifier_compte', {
utilisateur : utilisateur,
prenom : byId('nc_prenom').value,
nom : byId('nc_nom').value,
mail : byId('nc_mail').value,
});
}
}
// envoie un événement socket pour modifier le mot de passe
function modifier_password(utilisateur) {
if (byId('nc_ancien_password').value !== '' && verifier('nouveau_password')) {
socket.emit('modifier_password', {
nom_utilisateur : utilisateur,
ancien_password : byId('nc_ancien_password').value,
nouveau_password : byId('nc_nouveau_password').value
});
}
}
// télécharge les petits avatars et les affiche sur la page
function avatars_small(nom_utilisateur) {
for (var i = 1; i < 101; i++) {
byId('mod_avatar').innerHTML +=
'<image src="static/avatars_petits/'+i+
'.jpg" onclick="changer_avatar('+i+', \''+nom_utilisateur+'\')">';
}
}
// change l'avatar d'un utilisateur sur la page et envoie un événement socket
function changer_avatar(n, nom_utilisateur) {
byClass('avatar_profil_desktop')[0].innerHTML =
'<image src="static/avatars_grands/'+n+'.jpg" class="avatar_profil"></image>';
byClass('avatar_profil_mobile')[0].innerHTML =
'<image src="static/avatars_grands/'+n+'.jpg" class="avatar_profil"></image>';
socket.emit('changement_avatar', {
avatar : n,
nom_utilisateur : nom_utilisateur
});
}
// affiche un message d'erreur - mot de passe incorrect
socket.on('ancien_password_incorrect', function() {
byId('form_nc_ancien_password').className = 'form-group has-error';
byId('ancien_password_incorrect').style.display = 'inline';
});
/* réinitialise les champs des mots de passe et affiche un message confirmant
la modification du mot de passe */
socket.on('mod_password_ok', function() {
byId('form_nc_ancien_password').className = 'form-group';
byId('nc_ancien_password').value = '';
byId('nc_nouveau_password').value = '';
byId('ancien_password_incorrect').style.display = 'none';
byId('mod_password').style.display = 'none';
verifier('nouveau_password');
byId('password_success').style.display = 'block';
byId('coordonnees_success').style.display = 'none';
});
/* actualise les nouvelles coordonnées et affiche un message confirmant
la modification des coordonnées chez l'utilisateur qui les a modifiées */
socket.on('mod_compte_ok', function(nouveau) {
byId('info_prenom').innerHTML = 'Prénom : ' + nouveau.prenom;
byId('info_nom').innerHTML = 'Nom : ' + nouveau.nom;
byId('info_email').innerHTML = 'E-mail : ' + nouveau.mail;
if (!nouveau.broadcast) {
byId('nc_prenom').value = nouveau.prenom;
byId('nc_nom').value = nouveau.nom;
byId('nc_prenom_mobile').value = nouveau.prenom;
byId('nc_nom_mobile').value = nouveau.nom;
byId('nc_mail').value = nouveau.mail;
byId('mod_coordonnees').style.display = 'none';
byId('password_success').style.display = 'none';
byId('coordonnees_success').style.display = 'block';
}
});
Astuce
La fonction responsive()
est utilisée ici pour changer de classe bootstrap
la balise <div>
contenant les informations sur l’utilisateur. Lorsque la taille de la fenêtre est inférieure à 400 px
, cette balise peut prendre toute la largeur de l’écran car l’avatar ne s’affiche plus à sa gauche, mais au-dessus d’elle.
Et nous voilà en possession d’une page qui permet à l’utilisateur de changer ses coordonnées !