AJAX - Workshop Chat Anwendung Tutorial Ein Online-Chat braucht nicht unbedingt Java, wie Facebook und co zeigen. Mit der Integration verschiedener Techniken in AJAX ist es jedoch möglich, einen einfachen Onlinechat auf schnelle Weise in eine gewöhnliche Webseite zu integrieren. Wir zeigen Ihnen, wie es geht!

Ein schlichter Onlinechat ist eigentlich schwierig zu realisieren, da die neu eingegangenen Messages stets vom Server geladen werden müssen. Dies bedeutete bisher, dass die Seite neu geladen werden musste. Um dies zu umgehen, hat man auf andere Möglichkeiten gesetzt, wie zum Beispiel JavaApplets. Mit AJAX wird die Sache erleichtert, da man die Seiten nicht mehr neu im Browser laden muss. Die periodische Überprüfung nach neuen Messages kann über JavaScript und XMLHttpRequests mit der setTimeOut-Funktion gesteuert werden.

Hört sich einfach an – ist es im Grunde auch so. Bei Dr.Web wurde bereits einmal von mir ein Skript vorgestellt, mit dem auf einfache Art eine Shoutbox programmiert wurde. Dieses Skript soll nun Schritt-für-Schritt AJAX tauglich gemacht werden und zu einem Chat umfunktioniert werden.

Im Internet gibt es bereits OpenSource-AJAX-Chat-Anwendungen, zum Beispiel Lace und Treehouse. Unser Beispiel wurde entwickelt, um im Rahmen dieser Artikelserie AJAX näher zu bringen. Das Beispiel weist längst nicht alle Features auf, die möglich wären, ist so aber besser verständlich.

Rollen wir das Feld von hinten auf und beginnen wir mit dem so genannten Server Backend. In unserem Fall ist das eine MySQL Datenbank. Für unsere kleine Anwendung verwenden wir eine Tabelle Namens "ajaxChat", welche die Felder id, name, nachricht und date hat; diese kann mit folgender SQL-Anweisung erzeugt werden:

CREATE TABLE `ajaxChat` (
`id` LONGINT NOT NULL AUTO_INCREMENT ,
`name` VARCHAR( 60 ) NOT NULL ,
`nachricht` VARCHAR( 120 ) NOT NULL ,
`date` VARCHAR( 30 ) NOT NULL ,
PRIMARY KEY ( `id` )
);

Screenshot
Zunächst wird das Backend angegangen; zum einen die Datenbank, zum anderen das PHP-Skript, welches die XML-Antworten auf die http-Anfragen erzeugt.

Das PHP-Skript (nennen wir es ajaxchat.php) auf der Serverseite muss folgende Funktionalitäten aufweisen:

  • Verbindung zur Datenbank öffnen und schließen.
  • Nneue Einträge müssen in die Datenbank eingefügt werden können.
  • Neue Einträge müssen abgeholt werden können; als Erkennung dient eine eindeutige ID.

Die Ausgabe der Daten, das heißt die asynchrone Antwort des Servers erfolgt über XML; dieses wird dann über JavaScript und CSS im Browser dargestellt.

Die Datenbankverbindung kann über folgenden Zeilen-Code geöffnet werden; die Variablen müssen an die jeweilige Serverumgebung angepasst werden.

<?php
//wichtig; damit nicht gecached wird!
header( "Cache-Control: no-cache, must-revalidate" );
header( "Pragma: no-cache" );
//damit unser JavaScript die Daten auch als XML erkennt
header("Content-Type: text/xml");

$db_host = "localhost"; // Host
$db_user = "root"; // User
$db_password = ""; // Passwort
$db_name = "test"; // Datenbank

mysql_connect($db_host,$db_user,$db_password) or die(mysql_error());
mysql_select_db($db_name) or die(mysql_error());

Nachrichten auslesen
Die Funktion, mit der stets die aktuellsten Nachrichten aus der Datenbank extrahiert werden:

function getLatestEntries($latestID) {

$query = "SELECT id, name, nachricht, date FROM ajaxchat WHERE id > $latestID ORDER BY id DESC LIMIT 20";
$erg = mysql_query($query);
echo "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>";
echo "<messages>";
while($erg2=mysql_fetch_array($erg)) //Erzeugung der XML Ausgabe
{
echo "<message><id>".$erg2['id'];
echo "</id><name>".$erg2['name'];
echo "</name><nachricht>".$erg2['nachricht'];
//Formatierung des Timestamps
echo "</nachricht><date>".date("d.m.Y H:i",$erg2['date']);
echo "</date></message>";
}
echo "</messages>";
}

Bei dieser Funktion kommt es insbesondere auf die SQL-Abfrage an; über die Bedingung id > $latestID, werden lediglich die Einträge ermittelt, deren ID größer (d.h. deren Einträge "frischer" sind), ermittelt. Die Variable latestID wird, wie wir weiter unten sehen werden, bei dem http Requests über JavaScript übergeben.

Durch den Zusatz ORDER BY id DESC in der SQL Abfrage wird sichergestellt, dass die Einträge absteigend ausgegeben werden; LIMIT 20 beschränkt die Ausgabe auf die letzten zwanzig Einträge. Die Erzeugung des XML-Codes erfolgt schlicht über die echo-Funktion (eine Verwendung der DOMXML Funktionen von PHP wäre bei dem geringen Aufwand nicht gerechtfertigt).

Neue Einträge
Das erzeugen von neuen Einträgen in die Tabelle erfolgt über folgende Funktion:

function createNewEntry($name, $nachricht) {

//HTML Tags entfernen
$name = strip_tags($name, '');
$nachricht = strip_tags($nachricht,'');

$query = "INSERT INTO ajaxchat(name, nachricht, date)
VALUES ('$name','$nachricht','".time()."')";
echo "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>";
if (!mysql_query($query)) {
//nicht notwendig, dient nur der Fehlerkontrolle
echo "<createNewEntry>0</createNewEntry>";}
else {
echo "<createNewEntry>1</createNewEntry>";
}
}

Dabei erzeugt die Funktion createNewEntry wiederum eine XML-Antwort; dieser schenken wir aber keine Beachtung. Sie wurde lediglich zur Fehlerüberprüfung implementiert, so dass man über JavaScript, falls es einen MySQL-Fehler beim Erzeugen eines neuen Eintrag gäbe, dies entsprechend im Browser anzeigen könnte - wenn man denn wollte.

Skriptsteuerung
Da alle Anfragen an ein und dasselbe Skript geschickt werden (ajaxchat.php), benötigen wir noch ein paar Zeilen-Code, mit denen sichergestellt wird, dass bei den Aufrufen auch die jeweils angesprochene Funktion ausgeführt wird. Wir arbeiten hierbei mit Parametern, die in die http Anfragen über das XMLHttpRequest Objekt eingebaut sind.

//falls action=createNewEntry, Erzeugung eines neuen Eintrages
if ($_GET['action'] == "createNewEntry") {
createNewEntry($_GET['name'], $_GET['nachricht']);
}
//falls action=getLatestEntries, Ausgabe der neuesten Einträge
elseif ($_GET['action'] == "getLatestEntries") {
getLatestEntries($_GET['latestID']);
}
mysql_close(); //schließen der PHP Verbindung
?>


Das PHP-Skript, der gesamte Backend ist noch relativ harmlos. Bei unserer AJAX-Engine wird es kniffliger.

Die AJAX Engine

Screenshot
Im zweiten Schritt wird die AJAX-Engine aufgebaut; mit ihr werden die http-Anfragen erzeugt und die Ergebnisse im Browser dargestellt

Die AJAX-Engine hat folgende Aufgaben:

  • Erzeugung der http-Anfragen um neue Nachrichten vom Server zu erfragen-
  • Erzeugung der http-Anfragen, über die neue Einträge in der Datenbank gespeichert werden.

Da später das Empfangen von Daten automatisiert wird beziehungsweise über die setTimeOut-Funktion gesteuert wird, ist es notwendig, dass wir für unsere Anwendung zwei XMLHttpRequest-Objekte erzeugen. Damit wird sichergestellt, dass man gleichzeitig sowohl Daten empfangen als auch senden kann.

Erzeugung zweier XMLHttpRequest-Objekte
Das JavaScript zur Erzeugung der XMLHttpRequest-Objekte ist identisch mit dem aus Teil 2 dieser Artikelserie:

<script type="text/javascript">
var latestID = 0; //latestID als globale Variable; wichtig!

function createXMLHttpReqObj() { //erzeugt die XMLHttpRequest Objekte

// für Mozilla etc.
if(window.XMLHttpRequest) {
try { //Fehler abfangen
req = new XMLHttpRequest();
} catch(e) {
req = false;
}
// für den InternetExplorer
} else if(window.ActiveXObject) {
try {
req = new ActiveXObject("Msxml2.XMLHTTP");
} catch(e) {
try {
req = new ActiveXObject("Microsoft.XMLHTTP");
} catch(e) {
req = false;
}
}
}
return req;
}
// Initialisierung der beiden Objekte
var httpGetLatestEntries= createXMLHttpReqObj();
var httpCreateNewEntry = createXMLHttpReqObj();

Neueste Einträge abfragen
Bei der Abfrage nach den neuesten Einträgen wird die LatestID in der http-Anfrage mit übergeben. Diese ist eine globale Javascript-Variable, da ihr Wert aus Funktionen heraus geändert wird. Initialisiert wird sie mit dem Wert null. Die URL zu dem Skript ajaxchat.php müssen Sie natürlich an Ihre Umgebung anpassen.

Die Funktion getLatestEntries sieht wie folgt aus:

//Funktion, mit der Anfragen nach neuen Einträgen gesendet wird
function getLatestEntries() {
//Anfrage senden, wenn Status der letzten Anfrage "completed" ist, bzw. "nicht initialisiert" (d.h. erster Aufruf)
if (httpGetLatestEntries.readyState == 4 || httpGetLatestEntries.readyState == 0) {
//Übergabe der latestID
httpGetLatestEntries.open("GET","
http://localhost/ajax/ajaxchat.php?
action=getLatestEntries&latestID="+latestID, true);
httpGetLatestEntries.onreadystatechange = handleHttpGetLatestEntries;
httpGetLatestEntries.send(null);
}
}

Als Funktion, mit welcher der Browser die Serverantwort behandelt ist die Funktion handleHttpGetLatestEntries angegeben:

//Behandelt die Serverantwort
function handleHttpGetLatestEntries() {

//wenn Anfrage den Status "completed" hat
if (httpGetLatestEntries.readyState == 4) {
//ermitteln der Antwort
response = httpGetLatestEntries.responseXML.documentElement;
//ermitteln der Messages; Überführung in ein Array
messages = response.getElementsByTagName('message');

//wenn es mindestens eine neue Nachricht hat, dann wird diese angezeigt!
if(messages.length > 0) {

for (var i=messages.length-1; i>=0; i--) {
//Darstellung im Browser mit dem DOM
showEntry= document.getElementById("showEntries");
neuSpan = document.createElement('span');
neuSpan.setAttribute('class','entry'); //CSS Klasse
neuSmall = document.createElement('small');
neuNameDate = document.createTextNode(messages[i].getElementsByTagName
('date')[0].firstChild.nodeValue + ': ' + messages[i].getElementsByTagName('name')[0].firstChild.nodeValue +': ');
neuSmall.appendChild(neuNameDate);
neuSpan.appendChild(neuSmall);
neuSpan.appendChild(document.createElement('br'));
neuNachricht = document.createTextNode(messages[i].getElementsByTagName
('nachricht')[0].firstChild.nodeValue);
neuSpan.appendChild(neuNachricht);
neuSpan.appendChild(document.createElement('br'));
showEntry.insertBefore(neuSpan, showEntry.firstChild);
}

//Festlegung der neuen latestID; Zugriff auf die Werte über das DOM
latestID = messages[0].getElementsByTagName('id')[0].firstChild.nodeValue;
}
//erneuter periodischer Aufruf (eine Art Rekursion)
setTimeout('getLatestEntries();',3000); //Erneute Anfrage in drei Sekunden
}
}


Über diese wird, sofern die Anfrage erfolgreich war, die XML-Daten vom Server in ein Array (messages) überführt und im Browser dargestellt. Dies findet jedoch nur statt, wenn es auch neue Nachrichten zum Anzeigen gibt (wenn messages.length >0), das heißt wenn die Anfrage an das PHP-Skript auch eine neue Nachricht zurückgegeben hat. Ist dies der Fall, wird die latestID nach der Darstellung der neuen Nachrichten im Browser aktualisiert. Da dies aus einer Funktion heraus geschieht, wird klar, weshalb latestID eine globale Variable sein muss.

Über die setTimeOut-Funktion wird nun unsere periodische Überprüfung nach neuen Einträgen durch den Aufruf der Funktion getLatestEntries alle drei Sekunden initialisiert; dabei handelt es sich um eine Art "verzögerte Rekursion".

Neue Nachrichten eintragen
Die JavaScript Funktionen, über welche neue Einträge an den Server gesendet werden, sind ähnlich aufgebaut, wie die vorherigen Funktionen. Mit der ersten Funktion createNewEntry wird die http-Anfrage erzeugt, wobei ihr die Werte für die Felder name und nachricht aus dem HTML-Formular übergeben werden.

//neue Nachricht auf dem Server erzeugen; die Übergabe der Werte erfolgt über das HTML Formular
function createNewEntry(name, nachricht) {

//Anfrage senden, wenn Status der letzten Anfrage "completed" ist, bzw. "nicht initialisiert" (d.h. erster Aufruf)
if (httpCreateNewEntry.readyState == 4 || httpCreateNewEntry.readyState == 0) {
//URL für HTTP Anfrage; muss angepasst werden
url = 'http://localhost/ajax/ajaxchat.php?action=createNewEntry&name=' + name + '&nachricht=' + nachricht;
httpCreateNewEntry.open("GET", url, true);
httpCreateNewEntry.onreadystatechange = handleHttpCreateNewEntry;
httpCreateNewEntry.send();
}
}


Die Antwort vom Server wird über die Funktion handleHttpCreateNewEntry abgearbeitet; diese ist relativ kurz:

//behandelt die Antwort des Servers
function handleHttpCreateNewEntry() {
if (httpCreateNewEntry.readyState == 4) {
//nachdem eine neue Nachricht erfolgreich erzeugt wurde, wird diese angezeigt
getLatestEntries();
}
}

Nachdem die Daten erfolgreich in der Datenbank hinterlegt wurden, wird die Funktion getLatestEntries() wiederum aufgerufen, um sicherzustellen, dass die neue Nachricht auch sofort angezeigt wird.

Das Frontend
Nachdem nun die AJAX-Engine aufgestellt ist, wenden wir uns dem Frontend zu. Auch hier ist JavaScript gefragt. So können die Eingaben über JavaScript überprüft werden; die Darstellung der Nachrichten kann über eine CSS-Klasse beeinflusst werden.

Das Formular
Das Formular ist simpel gehalten; es gibt lediglich Eingabefelder und einen Submit-Button:

<form name="form1" method="post" action="">
<p>Name<br />
<input type="text" name="name" id="name">
<br />
<br />
Nachricht<br />
<input type="text" name="nachricht" id="nachricht">
</p>
<p>
<input type="submit" name="Submit" value="Submit" onclick="checkInput(document.form1.name.value, document.form1.nachricht.value)" >
</p>
</form>

Nachdem man auf den Submit-Button geklickt hat, wird die JavaScript-Funktion checkInput aufgerufen; an diese werden die eingegebenen Daten weitergegeben. In ihr wird die Eingabe überprüft.

Eine solche Überprüfung könnte im einfachsten Fall wie folgt aussehen:

function checkInput(name, nachricht) {
if(name != "" && nachricht != "") {
//Falls alles OK ist, kann der neue Eintrag erzeugt werden
createNewEntry(name, nachricht);
}
else {
alert("Bitte sowohl einen Namen, als auch eine Nachricht eingeben!");
}
}

Die Darstellung der Ergebnisse kann mit Hilfe von CSS beeinflusst werden. Dazu greifen wir auf die über das DOM zugewiesene CSS-Klasse "entry" zurück; über folgende Zeilen könnte man die Darstellung im Browser ein wenig ansehnlicher machen.

<style type="text/css">
.entry {
width: 100%;
background-color: #F5F5F5;
border: 1 dotted #666666;
font-family: Verdana;
}
.entry#small {
color: #CCCCCC;
}
</style>


So könnte das Ergebnis im Browser aussehen:

Screenshot
AJAX Chat in der konkreten Anwendung

Der Aufruf des Skriptes kann über einen onLoad-Event im Body-Tag der HTML-Seite (<body onload="getLatestEntries()">), in welcher das Formular eingebettet ist, eingeleitet werden; dabei wird die Funktion getLatestEntries das erste mal aufgerufen, wodurch die setTimeOut-Endlosschleife gestartet wird.

Alles in allem, war es ein langer Weg von der Idee zur konkreten Umsetzung. AJAX bedeutet nicht nur, dass die Webseiten in Zukunft in ihrem Verhalten normalen Desktop-Anwendung ähneln können, sondern bringt auch viel Arbeit mit sich. Vor allem das Zusammenspiel von JavaScript und XML klappt bislang nicht immer reibungslos.

Das hier vorgestellte Beispiel besitzt zudem einen Bug, der nicht verschwiegen werden darf. Auf der JavaScript Seite kann nicht mit Umlauten oder anderen Sonderzeichen umgegangen werden; diese verursachen einen Fehler. Alle Dateien gibt es wie immer als Paket zum Download.

(tf)
Dieser Artikel ist ebenfalls bei Dr.Web erschienen

Bookmark setzen... These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Print
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Bloglines
  • MisterWong
  • MySpace
  • Reddit
  • SEOigg
  • Technorati
  • TwitThis
  • Y!GG
  • Google Bookmarks

Weiterführende Links: