Mon premier Chat
PAR OLIVIER SERRE

Introduction

Le but de ce mémo est de vous expliquer comment réaliser un chat basique en utilisant jQuery (et plus précisément AJAX).

Vous pourrez télécharger le code complet tout en bas de la page. Je vous conseille de lire cependant ce qui suit car on y construit le code pas à pas…

Les fichiers principaux

Le fichier principal est très basique. Il comporte essentiellement deux zones: une pour la console et une pour le login/saisie de message.


<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta charset="utf-8">
        <title>Chat</title>
        <link href="css/chat.css" rel="stylesheet"/>
        <script src="js/jquery.min.js" type="text/javascript"></script>
        <script src="js/chat.js" type="text/javascript"></script>
    </head>
    <body>
        
        <h1>Mon premier chat</h1>
        
        <div id="console"></div>
        <div id="zoneLogin"></div>
        
    </body>
</html>
        

Le fichier chat.css est très basique également (la propriété overflow permet d'avoir un ascenceur sur le bord de la console):


h1{
    text-align: center;
}

#console{
    width:70%;
    height:300px;
    margin:auto;
    padding:10px;
    background-color: black;
    color:white;
    text-align: left;
    font-family: Courier,monospace;
    overflow:auto;
}

#zoneLogin{
    margin-top: 20px;
    width:70%;
    margin-left:auto;
    margin-right:auto;
    padding:10px;
    background-color: black;
    color:white;
    text-align: left;
    font-family: Courier,monospace;
}
        

Le cœur du travail va se situer sur le fichier chat.js.

Gestion du login/logout

On commence par se charger de l'affichage des fomulaires de login/logout:

       
 
//fichier chat.js
//la fonction suivante va se charger d'initialiser les boutons des formulaires se 
//trouvant dans la zone #zoneLogin
function initButton(){
}


$(document).ready(function() {
    $("#zoneLogin").load("utils/zoneLogin.php",initButton);
});
       
 

Notez qu'il est important d'utilise du callback pour initialiser les boutons des formulaires une fois ceux-ci affichés.

Le fichier zoneLogin.php produit le code des formulaires de login/logout. On utilise une variable de session pour se rappeler si l'on est loggé ou pas.

       
 
//fichier zoneLogin.php
<?php
session_start();
if(isset($_SESSION["login"])){//si on est loggé on affiche des infos + deconnexion
    echo "Loggé en tant que ".$_SESSION['login']."  ";    
    echo "<button id='buttonLogout'>Déconnexion</button><br/>";
}
else{
    echo "<form id='loginForm' action='#'>  ";
    echo "<input type='text' placeholder='Login' id='login' name='login'/>  ";
    echo "<input type='password' placeholder='Password' id='password' name='password'/>  ";
    echo "<input type=submit value='Connexion'/>";
    echo "</form>";
}
?>       
 

On commence par compléter un peu le fichier chat.js en précisant la fonction initButton. Vous remarquerez que l'on recharge la zone de login après toute tentative de connexion/déconnexion.

       
 
function initButton(){
    //fonction de connection
   $("#loginForm").submit(function(){
        $.post("utils/login.php",$("#loginForm").serialize(),function(reponse){
                $("#zoneLogin").load("utils/zoneLogin.php",initButton);
        });
        return false;//pour ne pas soumettre formulaire
    })

    //fonction de déconnection
    $("#buttonLogout").click(function(){
        $.post("utils/logout.php",function(){
            $("#console").html("");//on efface la console
            $("#zoneLogin").load("utils/zoneLogin.php",initButton);                    
        });
    })
}       
 

Avant de donner les scripts de connexion/déconnexion, on précise la structure de la base de données utilisateurs. C'est du classique (notez simplement que l'on supposera que le mot de passe est crypté via du SHA1).

       
 
CREATE TABLE `Users` (
  `login` varchar(40) NOT NULL,
  `password` varchar(40) NOT NULL,
  PRIMARY KEY (`login`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
       
 

Les scripts de connexion (fichier utils/login.php) et de déconnexion (fichier utils/logout.php) sont classiques. On suppose que l'on a également un fichier utils/BD.cls pour les fonctions relatives à la BD.

       
 
// fichier utils/BD.cls
<?php
    class Database {
        public static function connect() {
            $dsn = "mysql:dbname=chat";
            $user = "root";
            $password = "";
            $dbh = null;
            try {
                $dbh = new PDO($dsn, $user, $password,array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8") );
                $dbh->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION );
            }
            catch (PDOException $e){
            echo "Connexion échouée: ".$e->getMessage(); exit(0);
            }
            return $dbh;
        }
    }
?>
     
       
 
       
 
// fichier utils/login.php
<?php
    session_start();
    require("BD.cls");
    $dbh = Database::connect();

    $query = "SELECT * FROM Users WHERE login=? and password=SHA1(?)";
    $sth = $dbh->prepare($query);
    $sth->execute(array($_POST["login"],$_POST["password"]));
    
    if ($sth->rowCount()>0){//bon login/password
        $_SESSION["login"]=$_POST["login"];
        echo "1";// on retourne 1 si la connexion est OK
    }
    
    $dbh = null; // Déconnexion de MySQL

?>
       
 
       
 
// fichier utils/logout.php
<?php
    session_start();
    unset($_SESSION["login"]);
?>
       
 

À ce stade les fonctions de connexion/déconnexion fonctionnent. Il reste à gérer les messages…

Les messages

On ajoute une table dans la base de données. Un message aura un id (auto-incrémenté) et on se rappelera du login du posteur.

       
 
CREATE TABLE `Messages` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `message` varchar(200) NOT NULL,
  `login` varchar(60) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
       
 

Pour gérer le formatage des messages dans la console, on se donne un fichier utils/message.cls

       
 
<?php
    class Message {
        public $id;
        public $message;
        public $login;
        
        public function affiche(){
            echo $this->login." > ".$this->message."<br/>";
        }
    }
?>       
 

Une fois loggé, on souhaite pouvoir envoyer des messages. Pour cela on va ajouter un formulaire, ce qui amène à compléter (lignes 7 à 10 ci-dessous) le script utils/zoneLogin.php

       
 
//fichier zoneLogin.php
<?php
session_start();
if(isset($_SESSION["login"])){//si on est loggé on affiche des infos + deconnexion
    echo "Loggé en tant que ".$_SESSION['login']."  ";    
    echo "<button id='buttonLogout'>Déconnexion</button><br/>";
    echo "<form id='messageForm' action='#'>  ";//formulaire pour la saisie des messages
    echo "<input type='text' placeholder='Message' id='message' name='message'/>  ";
    echo "<input type=submit value='Envoyer'/>";
    echo "</form>";
}
else{
    echo "<form id='loginForm' action='#'>  ";
    echo "<input type='text' placeholder='Login' id='login' name='login'/>  ";
    echo "<input type='password' placeholder='Password' id='password' name='password'/>  ";
    echo "<input type=submit value='Connexion'/>";
    echo "</form>";
}
?>       
 

Il faut maintenant gérer l'action du bouton de soumission du formulaire d'id messageForm. Pour cela, on complète la fonction (lignes 12 à 22 ci-dessous) initButton du fichier chat.js. On y ajoute également une fonction recuppererMessages() à préciser plus tard.

       
 
function initButton(){
    //fonction de connection
   $("#loginForm").submit(function(){
        (…)
    })

    //fonction de déconnection
    $("#buttonLogout").click(function(){
        (…)
    })
    
    $("#messageForm").submit(function(){
        //on récupère le message
        var message = $("#message").val();
        if (message!=""){// si le message est non vide
            //on le stocke puis on récupère les nouveaux messages
            $.post("utils/save.php",{message: message},recuppererMessages);
            // on efface la zone de message
            $("#message").val("");
        }
        return false;//pour ne pas soumettre formulaire
    })
}

// récupère les nouveaux messages et les affiche
function recuppererMessages(){
}

       
 

On doit donc écrire un script utils/save.php qui stocke le message dans la base de données (table Messages).

       
 
// fichier utils/save.php
<?php
    session_start();
    require("BD.cls");

    $dbh = Database::connect();
    $query = "INSERT INTO Messages(message,login) VALUES (?,?)";
    $sth = $dbh->prepare($query);
    $sth->execute(array($_POST["message"],$_SESSION["login"]));
    $dbh = null; // Déconnexion de MySQL
?>       
 

Il reste essentiellement à écrire la fonction de récupération des messages. On commence par compléter dans le fichier chat.js.

       
 
// récupère les nouveaux messages et les affiche
function recuppererMessages(){
    $.post("utils/getMessages.php",{},function(reponse){
            $("#console").append(reponse).scrollTop(100000);
    });
}
       
 

L'instruction scrollTop(100000) va juste faire remonter l'ascenceur de sorte à ce que le dernier message (écrit tout en bas) soit toujours visible.

Il faut maintenant écrire le script utils/getMessages.php. La difficulté consiste à se rappeler du dernier message lu. Pour cela on va utiliser une variable de session $_SESSION["last"]. Il faut l'initialiser lors de la connexion (et la détruire lors de la déconnexion). L'idée est que lors de la connexion, on va simplement récupérer le plus grand id de message présent dans la table Messages. D'où les nouvelles versions des scripts utils/login.php (lignes modifiées: 13 à 22) et utils/logout.php

       
 
// fichier utils/login.php
<?php
    session_start();
    require("BD.cls");
    $dbh = Database::connect();

    $query = "SELECT * FROM Users WHERE login=? and password=SHA1(?)";
    $sth = $dbh->prepare($query);
    $sth->execute(array($_POST["login"],$_POST["password"]));
    
    if ($sth->rowCount()>0){//bon login/password
        $_SESSION["login"]=$_POST["login"];
        // on stocke l'id du dernier message à afficher
        $query = "SELECT id FROM Messages ORDER BY id DESC LIMIT 1";
        $sth = $dbh->prepare($query);
        $sth->execute(array());
        if ($ligne = $sth->fetch(PDO::FETCH_ASSOC)){
            $_SESSION["last"]=$ligne["id"];
        }
        else{
                    $_SESSION["last"]=0;
        }
        echo "1";// on retourne 1 si la connexion est OK
    }
    
    $dbh = null; // Déconnexion de MySQL

?>
       
 
       
 
// fichier utils/logout.php
<?php
    session_start();
    unset($_SESSION["login"]);
    unset($_SESSION["last"]);    
?>
       
 

Le script utils/getMessages.php est alors simple à écrire. Notez la mise à jour de $_SESSION['last'].

       
 
<?php
    session_start();
    require("BD.cls");
    require("message.cls");
    $dbh = Database::connect();

    $last = $_SESSION["last"];
    $query = "SELECT * FROM Messages WHERE id > $last ORDER BY id";
    $sth = $dbh->prepare($query);
    $sth->setFetchMode(PDO::FETCH_CLASS,"Message");
    $sth->execute(array());
    if ($sth->rowCount()>0){// il y a des nouveaux messages
        while ($nouveau = $sth->fetch()){
            $nouveau->affiche();
            $id = $nouveau->id;
        }
        $_SESSION["last"]=$id;
    }
    $dbh = null; // Déconnexion de MySQL
?>       
 

Récupérer les messages

Bon, tout marche bien sauf que l'on ne récupère les messages que lorsque l'on en poste un… Pour résoudre ce problème, on va utiliser la fonction setInterval pour appeler toute les secondes la fonction recuppererMessages. On a donc le nouveau code pour chat.js (les nouveautés sont lignes 2, 10 et 19 ci-dessous).

       
 
// Contiendra l'interval id pour le refresh du chat
var refreshIntervalId;

function initButton(){
    //fonction de connection
   $("#loginForm").submit(function(){
        $.post("utils/login.php",$("#loginForm").serialize(),function(reponse){
                $("#zoneLogin").load("utils/zoneLogin.php",initButton);
                if (reponse=="1"){// reponse = 1 -> succes
                    refreshIntervalId = setInterval(recuppererMessages,1000);
                }
        });
        return false;//pour ne pas soumettre formulaire
    })

    //fonction de déconnection
    $("#buttonLogout").click(function(){
        $.post("utils/logout.php",function(){
            clearInterval(refreshIntervalId);
            $("#console").html("");//on efface la console
            $("#zoneLogin").load("utils/zoneLogin.php",initButton);                    
        });
    })
    
    $("#messageForm").submit(function(){
        //on récupère le message
        var message = $("#message").val();
        if (message!=""){// si le message est non vide
            //on le stocke puis on récupère les nouveaux messages
            $.post("utils/save.php",{message: message},recuppererMessages); 
            // on efface la zone de message
            $("#message").val("");
        }
        return false;//pour ne pas soumettre formulaire
    })
}

// récupère les nouveaux messages et les affiche
function recuppererMessages(){
    $.post("utils/getMessages.php",{},function(reponse){
            $("#console").append(reponse).scrollTop(100000);
    });
}

$(document).ready(function() {
    $("#zoneLogin").load("utils/zoneLogin.php",initButton);
});
       
 

Code complet

Voilà, on a fini! Vous pouvez télécharger le code complet: chat.zip.