##### TP - page d'accueil ##### A présent, vous avez les connaissances de base nécessaires pour comprendre la plupart des fonctionnalités du forum ; nous pouvons effectuer un petit travail pratique ! Le but de ce TP est de réaliser la page suivante : .. image:: tp1/1.png Cette page est également ``responsive``. Donc, lorsque nous réduisons la taille de la fenêtre du navigateur web, nous obtenons le résultat suivant : .. image:: tp1/2.png :height: 10000 px :width: 700 px :scale: 60 % Il s'agit de créer une page sur laquelle s'afficheront les discussions. Vous allez devoir récupérer les informations suivantes depuis la base de données : - le sujet de la discussion - l'id de la discussion (il servira à donner le lien sur lequel la discussion pointera - par exemple : ``http://localhost:port/1`` renverra vers la première discussion) - l'utilisateur qui a créé la discussion (c'est-à-dire l'auteur du premier message de la discussion) - la date d'écriture de la discussion, formatée en français - le nombre de messages de la discussion Puisque l'application s'actualise en temps réel, lorsqu'une nouvelle discussion est créée, celle-ci s'affiche à la suite des discussions déjà existantes sans que la page aie besoin d'être chargée. Comme nous n'avons pas encore réalisé la création de nouvelles discussions, il faudra juste écrire une fonction anonyme qui sera appelée avec l'événement socket ``charger_derniere_discussion``. .. captionup:: ./static/js/index.js .. code-block:: javascript socket.on('charger_derniere_discussion', function() { // ... }); Cette page comprend également un mini moteur de recherche. Si nous tapons du texte dans la barre de recherche, seules les discussions dont le sujet contient partiellement ou entièrement le terme de recherche sont affichées : .. image:: tp1/3.png :height: 10000 px :width: 700 px :scale: 60 % Pistes ***** - Afficher une discussion nécessite beaucoup de formattage ``HTML``. Il est donc préférable de charger les discussions en utilisant ``EJS`` et ``AJAX``. En outre, vous pouvez déjà créer une fonction qui chargera la dernière discussion ; nous en aurons besoin par la suite pour recharger les discussions en temps réel. - Le moteur de recherche est uniquement codé en ``javascript``. Il n'est donc pas nécessaire d'interroger le serveur pour afficher les résultats d'une recherche. .. tip:: Pour chercher si une chaîne de caractères est présente dans le sujet d'une discussion, vous pouvez construire une expression régulière à l'aide de l'objet ``RegExp`` : .. code-block:: javascript var recherche = byId('recherche_discussion').value; var regex = new RegExp(recherche, 'i'); // i est l'option pour que l'expression régulière ne soit pas sensible à la casse .. only:: html - L'image à gauche du texte "créé par ..." peut être téléchargée à partir de `ce lien`_. - Vous pouvez utiliser le `Hello World`_ que nous avons précédemment créé pour avoir la base de votre page d'accueil. Il faut juste modifier la barre de navigation, le titre de la page et le nom de la base de données à laquelle vous voulez accéder. - Pour le fichier EJS principal, vous pouvez garder ``index.ejs`` .. only:: latex - L'image à gauche du texte "créé par ... le ..." peut être téléchargée à partir du lien suivant : http://j.mp/1Xvxhz3. - Vous pouvez utiliser le ``Hello World`` que nous avons précédemment créé pour avoir la base de votre page d'accueil. Il faut juste modifier la barre de navigation, le titre de la page et le nom de la base de données à laquelle vous voulez accéder. - Pour le fichier EJS principal, vous pouvez garder ``index.ejs`` .. warning:: N'oubliez pas de spécifier la nouvelle base de données dans le fichier sql.js .. code-block:: javascript var pool = mysql.createPool({ host : 'localhost', user : 'root', password : 'root', // spécifier la base de données ici : database : 'forum', charset : 'UTF8_UNICODE_CI', multipleStatements: true }); Bon travail ! Correction ***** Code côté serveur ---- Pour que cette page soit affichée correctement, il faut ``routes.js`` doit transmettre les donnéees des discussions qu'il a obtenues depuis la base de données. Voici la requête ``SQL`` utilisée : .. code-block:: sql SELECT COUNT(messages.id_message) AS "nombre_messages", discussions.sujet, discussions.id_discussion, messages.nom_utilisateur AS "utilisateur_createur", messages.date_ecriture FROM discussions, messages WHERE discussions.id_discussion = messages.id_discussion AND messages.id_discussion = --ici s'ajoutera l'id de la discussion-- ORDER BY messages.date_ecriture LIMIT 0,1 .. important:: Cette requête ``SQL`` a besoin d'un élément (``id_discussion``) lui permettant de charger toutes les informations nécessaires à l'affichage d'une discussion. Comme nous devons récupérer, au préalable, une liste contenant les identifiants de toutes les discussions, cela engendre un important "dialogue" entre le serveur et la base de données. Pour limiter ce problème, il est possible de réaliser des **transactions**. Nous n'aborderons pas ce sujet dans la création de cette application, mais notez qu'il serait tout à fait possible d'utiliser cette technique pour améliorer les performances de notre programme. Pour récupérer toutes les informations dont nous avons besoin, nous devons exécuter cette requête ``SQL`` autant de fois qu'il y a des discussions. Ces nombreuses requêtes pouvant surcharger le serveur, nous emploierons donc le ``;`` pour séparer les instructions ``SQL`` et nous utiliserons une boucle afin d'ajouter des instructions à la requête principale. Pour créer la boucle, nous avons besoin des ``id`` des discussions que nous souhaitons charger. Par ailleurs, il la requête doit être différente selon si nous voulons charger l'intégralité des discussions ou seulement la dernière. Pour différencier ces deux cas, nous utilisons la variable ``tout_charger``. Si elle est ``true``, la requête retourne tous les ``id`` des discussions ; dans le cas contraire, seul le dernier ``id`` de discussion apparaît. .. captionup:: ./serveur/sql.js .. code-block:: javascript if (tout_charger) { var requete_sql = '\ SELECT id_discussion\ FROM discussions\ ORDER BY id_discussion'; } else { var requete_sql = '\ SELECT id_discussion\ FROM discussions\ ORDER BY id_discussion DESC\ LIMIT 0,1'; } sql.requete(mysql, sql, requete_sql, function(results) { // ... .. note:: Les antislash qui se trouvent en fin de ligne permettent d'étendre une chaîne de caractères sur plusieurs lignes pour mieux structurer la requête. Maintenant, nous avons les éléments nécessaires pour la boucle : .. captionup:: ./serveur/sql.js .. code-block:: javascript sql.requete(mysql, sql, requete_sql, function(results) { var requete_sql = ''; for (var i = 0; i < results.length; i++) { var requete_temp = '\ SELECT\ COUNT(messages.id_message) AS "nombre_messages",\ discussions.sujet,\ discussions.id_discussion,\ messages.nom_utilisateur AS "utilisateur_createur",\ messages.date_ecriture\ FROM discussions, messages\ WHERE discussions.id_discussion = messages.id_discussion\ AND messages.id_discussion = ' + results[i].id_discussion + '\ ORDER BY messages.date_ecriture\ LIMIT 0,1;'; requete_sql = requete_sql + requete_temp; } // ... }); .. warning:: Ce code fonctionne, mais il serait vulnérable aux **injections **``SQL`` si les données à insérer dans la requête venaient de l'utilisateur. Nous étudirons ce que c'est et comment faire pour s'en prémunir au chapitre suivant. Nous pouvons alors envoyer toute la requête ``SQL`` à la base de données : .. captionup:: ./serveur/sql.js .. code-block:: guess sql.requete(mysql, sql, requete_sql, function(results) { if (tout_charger) { callback(results); } else { // met la variable results dans un array pour que les informations à traiter ensuite soient similaires callback([results]); } }); Il ne reste plus qu'à mettre ce code dans une fonction qui puisse être appelée facilement depuis ``routes.js`` : .. captionup:: ./serveur/sql.js .. code-block:: javascript exports.chargement_discussions = function(mysql, sql, tout_charger, callback) { if (tout_charger) { var requete_sql = '\ SELECT id_discussion\ FROM discussions\ ORDER BY id_discussion'; } else { var requete_sql = '\ SELECT id_discussion\ FROM discussions\ ORDER BY id_discussion DESC\ LIMIT 0,1'; } sql.requete(mysql, sql, requete_sql, function(results) { var requete_sql = ''; for (var i = 0; i < results.length; i++) { var requete_temp = '\ SELECT\ COUNT(messages.id_message) AS "nombre_messages",\ discussions.sujet,\ discussions.id_discussion,\ messages.nom_utilisateur AS "utilisateur_createur",\ messages.date_ecriture\ FROM discussions, messages\ WHERE discussions.id_discussion = messages.id_discussion\ AND messages.id_discussion = ' + results[i].id_discussion + '\ ORDER BY messages.date_ecriture\ LIMIT 0,1;'; requete_sql = requete_sql + requete_temp; } sql.requete(mysql, sql, requete_sql, function(results) { if (tout_charger) { callback(results); } else { /* met la variable results dans un array pour que les informations à traiter ensuite soient similaires */ callback([results]); } }); }); } Puis, nous appelons cette fonction dans ``routes.js`` : .. captionup:: ./serveur/routes.js .. code-block:: javascript // la page se charge aussi si nous entrons l'adresse http://localhost:port/home app.get(/^\/home|\/$/, function(req, res) { sql.chargement_discussions(mysql, sql, true, function(discussions) { res.render('index.ejs', { nom_utilisateur: session.session_active(req.session, req), discussions: discussions }); }); }); Code côté client ----- Vous constatez que le fichier principal a peu de lignes de code. Et pour cause, il utilise beaucoup de ``<% include %>`` : .. captionup:: ./views/index.ejs .. code-block:: guess
<% include balise_head %> <% include barre_navigation %>
<% include chargement_discussions %>
![]() |
<%= discussions[i][0].sujet %>
créé par <%= discussions[i][0].utilisateur_createur %> le <% convertir_date(discussions[i][0].date_ecriture); %> |