1. Funktionen und Techniken Die Shoutbox, die am Ende dieser Anleitung entstanden sein wird, besteht aus validem XHMTL, schick gemacht mit CSS. Funktionen zum Abrufen und Speichern der einzelnen Nachrichten werden auf dem Webserver, der unbedingt erforderlich ist, mit PHP realisiert. Sämtliche Daten werden in einer mySQL-Datenbank gespeichert. Mit AJAX, genauer mit JavaScript und dem XmlHttpRequest-Objekt, entsteht das Herzstück der Anwendung. Der Chat läuft auf allen aktuellen Versionen von Internet Explorer, Firefox und Opera. 2. Das Front-End Die Shoutbox selbst ist schnell gezimmert: Ein paar Block-Elemente, Eingabefelder und ein Button zum Absenden reichen vollkommen aus. Grundsätzlich müssen zwei Bereiche eingerichtet werden: Einerseits ein Bereich zum Anzeigen bisheriger Nachrichten, ein weiterer zum Eingeben von Name und Nachricht. In diesem Tutorial soll das folgendermaßen aussehen: Oder in Form von w3c-konformem XHTML-Code: <div id="asb_container"> <div id="asb_contentwrap"> <div id="asb_content"> Shoutbox wird geladen ... </div> </div> <div id="asb_inputwrap"> <div id="asb_input"> <form action="" name="frmshoutbox" onsubmit="saveData(); return false;"> <b>Name:</b><br /><input class="text" type="text" name="txtname" value= "" /><br /> <b>Nachricht:</b><br /><input class="text" type="text" name="txtmessage " value="" /><br /> <input class="button" type="submit" name="btnsend" value="Senden" /> </form> </div> </div> </div> Einzelne Elemente wurden mit eindeutigen Namen versehen, damit es CSS und JavaScript leichter fällt, Teile der Shoutbox zu identifizieren und anzusprechen - mehr dazu gleich. Die Ausgabe bisheriger Nachrichten erfolgt in einem div mit der id asb_content. Die Eingabe erfolgt über eine form mit zwei Éingabefeldern. Drückt man in selbigen die Enter-Taste oder betätigt man den Senden-Button, wird die Nachricht verschickt. Im momentanen Zustand sieht die Shoutbox noch relativ nackt aus. Ein Ansatz von XHTML war es ja schließlich auch, schmückende Elemente aus HTML zu entfernen und nur noch strukturierend tätig zu sein. Nun darf CSS seine Macht entfalten, mit diesem Stylesheet: form { margin: 0px; } #asb_container { border: 1px dashed #B52021; width: 160px; } #asb_contentwrap { font: 8pt Arial; height: 200px; background-color: #DDDDDD; overflow: auto; } #asb_content { margin: 5px; } #asb_content .name { color: #555555; font-weight: bold; padding-right: 5px; } #asb_inputwrap { font: 8pt Arial; } #asb_input { margin: 5px; } #asb_input .text { border: 1px solid #888888; font: 8pt Arial; width: 146px; background-color: #f4f4f4; } #asb_input .button { width: 50px; margin-top: 4px; background-color: #B52021; font: bold 8pt Arial; color: white; border: 1px solid #888888; } Grundsätzlich sind zum CSS-Teil nur zwei Dinge anzumerken: 1. Dem form-Tag wurde der hauseigene Außenrand entzogen, der sich gern mal unansehnlich ausbreitet. 2. Die Definition asb_contentwrap beinhaltet overflow: auto. Damit wird automatisch eine vertikale Scrollbar angezeigt, sollten einmal die darzustellenden Nachrichten nicht in die feste Größe der Shoutbox passen. 3. Nachrichten speichern Sämtliche Nachrichten werden in einer mySQL-Datenbank gespeichert, eine einzige Tabelle genügt, nennen wir sie treffenderweise shoutbox. Sie enthält die Felder name, message und, wie sich das gehört, id als Primärschlüssel. In SQL ausgedrückt, sieht das so aus: CREATE TABLE shoutbox ( id int(11) NOT NULL auto_increment, name varchar(50) NOT NULL default '', message tinytext NOT NULL, PRIMARY KEY (id) ) 4. Ein- und Auslesen von Nachrichten Zwei PHP-Skripten kümmern sich nun darum, die eben erstellte Datenbanktabelle mit Nachrichten zu füllen und selbige auch wieder zu lesen. Ein drittes Skript beinhalt alle Einstellungen für den serverseitigen Bereich der Anwendung, nennen wir es config.php: <? //Datenbank-Einstellungen $dbhost='localhost'; $dbuser='username'; $dbpass='password'; $db='db1'; $dbtable='shoutbox'; //Anzahl der auszugebenden Nachrichten $messages_count = 10; ?> $messages_count soll das Ausgeben bereits bestehender Nachrichten auf eine gewisse Anzahl einschränken. Ansonsten würde getdata.php bei jeder Anfrage alle Datensätze ausgeben und unnötig Traffic verursachen. Erschwerend kommt hinzu, dass getdata.php relativ oft aufgerufen wird, mehr dazu gleich. Das Skript baut, basierend auf den Einstellungen aus config.php, eine Datenbankverbindung auf. Mittels SQL werden die neuesten Nachrichten aus der Datenbanktabelle gefischt und ausgegeben. Die Ausgabe erfolgt mit Hilfe von HTML-Tags. Das ist nicht unbedingt die professionellste Variante, schließlich kümmert sich ein serverseitiges Datenbankabfrageskript um die optische Gestaltung einer browserseitigen Angelegenheit. Aber wollen wir es nicht komplizierter machen, als es sein muss. Übrigens: Manch’ Browser packt getdata.php bei einem Aufruf in den Cache und weigert sich, bei einem nächsten Aufruf die Datei erneut vom Server zu laden um die lokal gecachte Version zu nutzen. Das Todesurteil für jede chatähnliche Anwendung. Daher beginnt das Skript mit einigen Funktionen, die den Browser von diesem Blödsinn abzuhalten. <?php //Cachen verhindern header("Expires: Sat, 05 Nov 2005 00:00:00 GMT"); header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); header("Cache-Control: no-store, no-cache, must-revalidate"); header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache"); //Einstellungen laden include("config.php"); //Verbindung zu mySQL aufbauen $dblink = mysql_connect($dbhost, $dbuser, $dbpass); if (!$dblink) { die('Keine Verbindung zur Datenbank möglich.'); } //Datenbank auswählen $dbselected = mysql_select_db($db, $dblink); if (!$dbselected) { die ('Kann Datenbank nicht erreichen.'); } //Datensätze ermitteln $dbresult = mysql_query("SELECT * FROM $dbtable ORDER BY ID DESC LIMIT 0,$m essages_count"); if (!$dbresult) { die('Ungueltige SQL-Query.'); } //Datensätze auslesen und formatieren while ($row = mysql_fetch_assoc($dbresult)) { echo "<span class="name">".$row["name"].":</span>"; echo $row["message"]."<br />"; } //Verbindung zur Datenbank schließen mysql_close($dblink); ?> setdata.php schreibt neue Nachrichten in die Datenbanktabelle shoutbox. Dabei ist zu beachten, dass die Daten per POST-Methode aus dem Eingabebereich der Shoutbox übertragen werden. <?php //Einstellungen laden include("config.php"); //Verbindung zu mySQL aufbauen $dblink = mysql_connect($dbhost, $dbuser, $dbpass); if (!$dblink) { die('Keine Verbindung zur Datenbank möglich.'); } //Datenbank auswählen $dbselected = mysql_select_db($db, $dblink); if (!$dbselected) { die ('Kann Datenbank nicht erreichen.'); } //Neuen Datensatz speichen $result = mysql_query("INSERT INTO $dbtable (name, message) VALUES ('".$_PO ST["name"]."','".$_POST["message"]."')"); if (!$result) { die('Ungueltige SQL-Query'); } //Verbindung zur Datenbank schließen mysql_close($dblink); ?> 5. Die AJAX-Schaltzentrale Die lästigen Vorarbeiten sind getan, kommen wir nun zum Herzstück der Shoutbox. AJAX nimmt die Eingaben aus den Eingabefeldern entgegen und versorgt setdata.php mit der neuen Nachricht. AJAX sorgt aber auch dafür, ständig die neuesten Nachrichten auf dem Schirm zu holen - vielleicht ja von anderen Nutzern der Shoutbox. Dafür wird regelmäßig getdata.php abgerufen. Das beste daran: Die Seite, auf der die Shoutbox enthalten ist, muss dafür nicht ständig neu geladen werden, wie es ohne AJAX nötig wäre. Man stelle sich nur einmal vor, die Shoutbox befände sich nicht allein auf einer Website, sondern zusammen mit anderen Texten, Bildern, usw. Das ist wohl der wahrscheinlichere Fall, nicht wahr?! Jeder Besucher würde wohl bald flüchten, müsste er alle paar Sekunden ein Neuladen der kompletten Site ertragen. Bevor wir munter dank AJAX mit den PHP-Skripten auf dem Server kommunizieren können, müssen Vorbereitungen getroffen werden. Das XMLHttpRequest-Objekt, Basis von AJAX, muss beim Aufrufen der Seite erstellt werden, um es später nutzen zu können. Dazu wird eine Instanz erstellt und der globalen Variable xmlHttp zugewiesen. Beim Internet Explorer ist XMLHttpRequest ein ActiveX-Objekt, bei der Konkurrenz ist es Bestandteil des Browsers. Hinzu kommt, dass das XMLHttpRequest-Objekt beim IE unterschiedlich angesprochen werden kann. Diesen Umständen wird z.B: mit try-catch-Blöcken Rechnung getragen: Schlägt eine Möglichkeit, XMLHttpRequest zu instanziieren, fehl, wird eben die nächste probiert. Sollte das Erstellen der Instanz letzlich doch geglückt ist, werden die neuesten Nachrichten vom Server geladen. Stand eben noch “Shoutbox wird geladen…” (siehe HTML-Template oben) auf dem Schirm, breiten sich jetzt Namen und Texte aus - sofern vorhanden. Die JavaScript-Funktion loadData kümmert sich darum. Damit immer wieder und wieder ein Nachrichten-Update erfolgt, wird diese Funktion regelmäßig abgerufen. Dazu nutzen wir setInterval. //globale Instanz von XMLHttpRequest var xmlHttp = false; //XMLHttpRequest-Instanz erstellen //... für Internet Explorer try { xmlHttp = new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) { try { xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) { xmlHttp = false; } } //... für Mozilla, Opera, Safari usw. if (!xmlHttp && typeof XMLHttpRequest != 'undefined') { xmlHttp = new XMLHttpRequest(); } //aktuelle Daten laden loadData(); //alle 5 Sekunden neue Daten holen setInterval("loadData()",5000); Was genau passiert nun in loadData? xmlHttp wird beauftragt, die Datei getdata.php vom Server abzurufen und bei Erfolg den Inhalt in der Shoutbox auszugeben. Über die Eigenschaft readyState erfahren wir, wann genau dieser Moment eingetroffen ist. readyState hat dann den Wert 4. Ändert sich diese Eigenschaft, tritt das Ereignis onreadystatechange ein, welches wir mit einer Funktion (in diesem Beispiel in Form einer inline-Funktion) behandeln. Über die Methode getElementById steuern wir unseren div-Tag mit dem Namen asb_content an und verändern die Eigenschaft innerHTML. Dort stehen dann die neuen Nachrichten. function loadData() { if (xmlHttp) { xmlHttp.open('GET', 'getdata.php', true); xmlHttp.onreadystatechange = function () { if (xmlHttp.readyState == 4) { document.getElementById("asb_content").innerHTML = xmlHttp.res ponseText; } }; xmlHttp.send(null); } } Interessant an dieser Stelle: Das erste A in AJAX steht für Asynchronous. Das heißt, sämtliche Abläufe auf der Website laufen unabhängig von unserer Anfrage an den Server weiter. Liefe der Prozess synchron weiter, müssten die Abläufe auf der Website gestoppt werden, bis die Daten komplett übertragen worden sind. Um aber dennoch die Kontrolle über den Datentransfer zu behalten, nutzt man die Ereignisbehandlung. Um eine Nachricht an den Server zu senden, versorgt die JavaScript-Funktion saveData das Skript setdata.php per POST-Methode mit den Benutzereingaben. Wir nutzen POST, um den Beschränkungen von GET (maximal 256 Zeichen Text) aus dem Weg zu gehen. Die Daten werden in Form von Parametern weiter gegeben, die die Methode send des XMLHttpRequest entgegen nimmt. savedata.php schreibt die erhaltenen Daten in die Datenbank. Es gibt noch ein Problem: Normalerweise wird eine Seite neu geladen, wenn man in einem HTML-Formular Enter drückt oder den Senden-Button betätigt. Das Neuladen wollen wir natürlich unbedingt verhindern, dennoch nicht auf die Annehmlichkeiten etwa der Enter-Taste verzichten. Das onsubmit-Ereignis einer form wird ausgelöst, sobald das Formular abgeschickt werden soll. Da klinken wir uns ein und leiten an dieser Stelle zu unserer Funktion saveData um. Diese verschickt die Daten und kehrt zur Behandlung von onsubmit zurück. Da damit aber alles erledigt ist, was wir wollen, brechen wir einfach die weitere Abarbeitung des Formulars ab. Dies erfolgt mit return false. Vielleicht haben ja einige diesen “Hack” bereits ganz am Anfang im HTML-Teil entdeckt? Damit das Ganze dennoch wirkt, wie von einem Formular gesendet, setzen wir mit der Methode setRequestHeader eine entsprechenden Header. So kommt setdata.php mit den übergebenen Daten hunderprozentig klar. Die Funktion saveData komplett: function saveData() { if (xmlHttp) { xmlHttp.open('POST', 'setdata.php'); xmlHttp.setRequestHeader('Content-Type', 'application/x-www-formurlencoded'); xmlHttp.send('name='+document.frmshoutbox.txtname.value+'&message='+do cument.frmshoutbox.txtmessage.value); } document.frmshoutbox.txtmessage.value = ''; document.frmshoutbox.txtmessage.focus(); } Am Ende der Funktion wird dafür gesorgt, dass das Nachrichteneingabefeld geleert wird und bereit für neue Eingaben ist. Um das zu vereinfachen, wird zu guter letzt noch der Fokus dorthin versetzt. 6. Erweiterungsmöglichkeiten Das obige Beispiel ist recht einfach gehalten und offen für Erweiterungen aller Art. Wie wäre es etwa, noch Datum und Zeit jeder Nachricht anzuzeigen? Ein echtes Highlight wäre zweifelsohne auch, diese AJAX-Applikation noch AJAXkonformer zu machen - indem getdata.php die Daten nicht als HTML-Schnippsel zurückgibt sondern als XML-Datei. Dann müsste sich JavaScript darum kümmern, die XML-Daten zu verarbeiten.