Shoutbox w php, mysql i jQuery

Tutorial opisuje tworzenie od podstaw prostego shoutboxa. Najpierw zbudujemy bazę danych. Nazwiemy ją się chat_db. Następnie stwórzmy w niej tabelę wiadomości. Poniższy rysunek pokazuje strukturę tej tabeli w phpmyadminie. Struktura tabeli wiadomości Jak widać tabela ma cztery pola: id, autor, treść i czas (oznacza datę i godzinę dodania wiadomości). Stwórzmy teraz plik index.php (tu będzie się wyświetlał nasz shoutbox).

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Chat</title>
<script type="text/javascript"
src="jquery-1.4.2.min.js"></script>
<script type="text/javascript">
// (js) tu będą skrypty js
</script>
<style type="text/css">
/*podstawowe style określające wygląd shoutboxa*/
body {font: 11px Arial, Helvetica, sans-serif; }
#chat_area {
width: 400px;
height: 300px;
background: #eee;
border: 1px solid #9F9F9F;
border-width: 1px 1px 0 1px;
}
#chat_area ul {
margin: 0;
padding: 0;
}
#chat_area ul li{
border-bottom: 1px solid #9F9F9F;
list-style-type: none;
padding: 4px;
font: 11px Arial, Helvetica, sans-serif;
}
.formularz{
background: #eee;
width: 380px;
border: 1px solid #9F9F9F;
border-width: 0 1px 1px 1px; 
padding: 10px;  
}
.formularz input, .formularz textarea{
font:  11px Arial, Helvetica, sans-serif; 
border: 1px solid #9F9F9F; 
}
</style>
</head>
<body>
<div id="chat_area">  <ul> 
<?php
//kod php wyświetlający shoutbox
?>
</ul>
</div>
<form action="" method="post" class="formularz">
<div><div>Imię:</div> <input type="text" name="imie" size="20"/></div>
<p><div>Treść:</div> <textarea cols="40" rows="5" name="tresc"></textarea></p>
<p><input type="submit" value="Wyślij" /></p>
<input type="hidden" name="chat" value="1" />
</form>
</body>
</html>
W sekcji head wpisujemy linię dodającą bibliotekę jQuery oraz kolejny znacznik script, w którym będziemy pisać skrypty javascript. Następnie trochę stylów css, których nie będę tu szerzej omawiać. Nasz shoutbox umieścimy w bloku o id chat_area. Wpisy na shoutboxie będą listą nieuporządkowaną. Na końcu strony wyświetlamy formularz dodawania nowej wiadomości. W miejscu na kod php, które zostawiliśmy powyżej połączmy się z bazą danych i wypiszmy 10 ostatnich wpisów.
$dbhost = "localhost";
$dbuser = "user";
$dbpass = "pass";
$dbname = "chat_db";
$wyswietlane = 10;
$dbconn = @mysql_connect($dbhost,$dbuser,$dbpass);
if($dbconn)
{
    mysql_select_db($dbname,$dbconn);
    mysql_query("SET NAMES utf8");
    $chat_items_num = mysql_query("SELECT COUNT(*) FROM wiadomosci");
    $chat_items_num = mysql_fetch_row($chat_items_num);
    $start = $chat_items_num[0] - 10;
    $chat_items = mysql_query("SELECT * FROM wiadomosci ORDER BY id LIMIT $start, $wyswietlane");

    while($wiadomosc = mysql_fetch_assoc($chat_items))
    {
         echo '<li>';
         echo date("d.m.Y H:i",strtotime($wiadomosc['czas'])).' <strong>'.htmlspecialchars($wiadomosc['autor']).'</strong> 			'.htmlspecialchars($wiadomosc['tresc']);
         echo '</li>';   
    }
}
Najpierw łączymy się z bazą poleceniem mysql_connect, wybieramy bazę i obliczamy ilość wszystkich wiadomości ($chat_items_num). Ponieważ chcemy wypisać 10 ostatnich wpisów w kolejnym zapytaniu stosujemy klauzule ORDER BY oraz LIMIT. Klauzuli LIMIT przekazujemy jako start liczbę wszystkich wpisów pomniejszoną o 10, a jako liczbę do wyświetlenia oczywiście 10. Po pobraniu interesujących nas wiadomości przechodzimy pętlą przez znalezione wpisy i wyświetlamy je jako elementy listy. Należy zwrócić uwagę na wykorzystanie funkcji htmlspecialchars() na wyświetlanych danych. Będziemy przecież pobierać dane od użytkownika i musimy je filtrować.

Plik przetwarząjący dane ajaxowo nazwijmy process_ajax.php. Będzie on odpowiedzialny za dynamiczne dołączenie nowej wiadomości. Fragment pliku widzimy na listingu poniżej.
header("Cache-Control: no-cache");
date_default_timezone_set('Europe/Warsaw');
$dbhost = "localhost";
$dbuser = "user";
$dbpass = "pass";
$dbname = "chat_db";
$wyswietlane = 10;
$dbconn = @mysql_connect($dbhost,$dbuser,$dbpass);
if($dbconn)
{
    mysql_select_db($dbname,$dbconn);
    mysql_query("SET NAMES utf8");
    if(isset($_POST['chat']) && $_POST['chat']==1)
    {
         // (1) tutaj dodamy przetwarzanie po wysłaniu przez nas nowej wiadomości
    }
    else 
    {
	// (2) tutaj przetwarzanie jeśli nie wysyłamy nowej wiadomości
    }
}
Na początku łączymy się z bazą, następnie w zależności czy wysłaliśmy wiadomość wykonuje się odpowiedni kod. W praktyce będzie to wyglądało następująco: kod jQuery (napiszemy go za chwilę) odpalony dla zdarzenia wysłania formularza będzie uruchamiał pierwszą część if-a, natomiast kod uruchamiany co 5 sekund drugą. Będzie sprawdzał czy dodano jakieś nowe wiadomości i jeśli tak automatycznie je wyświetli w shoutboxie. W miejsce (1) wstawiamy następujący kod.
$nick = mysql_real_escape_string($_POST['imie']);
$tresc = mysql_real_escape_string($_POST['tresc']); 
mysql_query("INSERT INTO wiadomosci (autor,tresc,czas)
VALUES ('$nick','$tresc',NOW())",$dbconn);
$last_id = mysql_insert_id();
$last_item = mysql_query("SELECT * FROM wiadomosci WHERE id = '$last_id'");
$wiadomosc = mysql_fetch_assoc($last_item);
echo '<li>';
echo date("d.m.Y H:i",strtotime($wiadomosc['czas'])).' <strong>'.htmlspecialchars($wiadomosc['autor']).'</strong> '.htmlspecialchars($wiadomosc['tresc']);
echo '</li>';
Pod zmiennymi $nick i $tresc zapisujemy oczyszczone dane przekazane przez użytkownika. Potem wykonujemy zapytanie INSERT INTO, które dodaje nam nowy wiersz do bazy danych. Potrzebujemy identyfikator wstawionej przed chwilą wiadomości i tu z pomocą przychodzi nam funkcja mysql_insert_id(). Na końcu pobieramy wszystkie dane dotyczące wstawionej przed chwilą wiadomości i wyświetlamy odpowiedni element li. Tak przygotowane dane odbierzemy później za pomocą jQuery i zaktualizujemy wyświetlane informacje na shoutboxie.
Teraz dodamy kod jQuery powiązany z powyższym kodem php. W miejscu na skrypty Javascript (js) dodajemy następujący kod.
$(function() {
	$(document).ajaxError(function() 
	{
		alert('Nie można wysłać danych lub błąd serwera!');
	}); 
	$('.formularz').submit(function() {      
	if($(this).find('input[name=imie]').val() && $(this).find('textarea').val()) 
	{            		   $.post('process_ajax.php',$(this).serialize(),function(dane){		
 $(this).find('input[name=imie],textarea').val('');
	$("#chat_area ul li:first").remove();
	$('#chat_area ul').append(dane);      
	});
	}
	return false;
	});
});
Kod ten będzie się wykonywał po załadowaniu drzewa dokumentu. Na początku przechwytujemy błędy ajaxa (ajaxError). Następnie gdy zajdzie zdarzenie submit formularza sprawdzamy czy pola formularza są wypełnione i jeśli tak łączymy się ajaxowo z plikiem process_ajax.php, przekazujemy mu dane z formularza $(this).serialize(). Plik process_ajax.php przetwarza je i zwraca nam odpowiedni rezultat w postaci kodu html do dołączenia do listy. Dołączenie wykonujemy metodą append. Ponadto usuwamy pierwszy element listy (chcemy aby na shoutboxie zawsze widoczne było tylko 10 najnowszych wiadomości).
$('#chat_area ul li:first').remove();
Możemy jeszcze wyczyścić pola formularza poleceniem poniżej.
$(this).find('input[name=imie], textarea').val('');
Teraz zajmiemy się trudniejszą częścią budowy shoutboxa (obsługa sytuacji, gdy nie dodajemy nowej wiadomości, tylko "siedzimy" na shoutboxie i czekamy co napiszą inni), mianowicie sprawdzanie czy w tabeli zaszły jakieś zmiany i odpowiednie dodawanie nowych wiadomości do wyświetlanej listy. Procedura ta będzie wykonywana co 5 sekund.
Zatem do dzieła :)
Będziemy wstawiać kod php w miejsce (2), które zostawiliśmy na jednym z powyższych listingów. Zaczniemy od pobrania daty ostatniej wiadomości z bazy danych. Po pobraniu zamieniamy datę w postaci tekstowej na unix timestamp.
$data_ostatniej_wiadomosci = mysql_query("SELECT MAX(czas) FROM wiadomosci");
$data_ostatniej_wiadomosci = mysql_fetch_row($data_ostatniej_wiadomosci);
$data_ostatniej_wiadomosci = strtotime($data_ostatniej_wiadomosci[0]);
Przygotujmy sobie zmienną $html, w której będziemy przechowywać kod html do zwrócenia użytkownikowi oraz zapiszmy pod zmienną $data_od_usera datę ostatniej wiadomości od użytkownika (jest to data ostatniej wiadomości, którą widzi użytkownik).
$html = '';
$data_od_usera = intval($_POST['data_ostatniej_wiadomosci']);
Teraz najważniejszy fragment tej części skryptu.
if($data_ostatniej_wiadomosci > $data_od_usera)
{
	$items = mysql_query("SELECT * FROM wiadomosci ORDER BY czas DESC LIMIT 10");
	$wyniki = array();
	while($wiadomosc = mysql_fetch_assoc($items))
	{
		$wyniki[] = $wiadomosc;
	}
	$wyniki = array_reverse($wyniki);
	foreach($wyniki as $wiadomosc)
	{
		$html .= '<li>';
		$html .= date("d.m.Y H:i",strtotime($wiadomosc['czas'])).' <strong>'.htmlspecialchars($wiadomosc['autor']).'</strong> '.htmlspecialchars($wiadomosc['tresc']);
		$html .= '</li>'; 
	}
}
else
{
	$html .= '';
}
Jeśli data ostatniej wiadomości z bazy jest większa od daty otrzymanej od usera należy pobrać wiadomości, których użytkownik nie widzi, a które są już w bazie. W przeciwnym razie zwracamy pusty ciąg znaków. Na koniec zwracamy dane JSON następującej postaci.
header('Content-Type: application/json');
echo json_encode(array('html'=>$html,'czas'=>$data_ostatniej_wiadomosci));
Pozostaje tylko dopisać kod jQuery. Napiszemy funkcję updateChat(), która będzie wywoływana co 5 sekund tzn.
setInterval('updateChat()',5000);
Najpierw zadeklarujmy zmienną globalną przechowującą datę ostatniej wiadomości. Na początku dajmy jej wartość 0, więc po pierwszym otworzeniu strony użytkownik będzie pobierał wszystkie wpisy.
var data_ostatniej_wiadomosci = 0;
Kod funkcji updateChat() przedstawia poniższy listing.
function updateChat()
{
	$.post('process_ajax.php',{data_ostatniej_wiadomosci: data_ostatniej_wiadomosci},function(dane){
	if(dane)
	{
		data_ostatniej_wiadomosci = dane.czas;
		if(dane.html!='')
		{
			$('#chat_area ul').html(dane.html);
		}
	}
	});
}
Wysyłamy metodą POST do pliku process_ajax.php datę ostatniej wiadomości, którą widzi użytkownik. Serwer zwróci nam obiekt JSON, który przetwarzamy tzn. zapisujemy pod zmienną data_ostatniej_wiadomosci datę ostatniej wiadomości zwróconą przez serwer (dane.czas). Jeśli dane html nie są pustym ciągiem znaków wstawiamy je do elementu ul. Kody źródłowe shoutboxa.