19.8 Ajax
Mithilfe der im vorigen Abschnitt vorgestellten DOM-Technik können Sie Inhalte bestehender HTML-Seiten nach Belieben umbauen, ohne diese neu laden zu müssen. Die Ajax-Technik kombiniert dies mit Anfragen an Webserver, die keine ganze Seite neu laden, sondern textbasierte oder XML-basierte Inhalte anfordern. Diese können Sie dann mithilfe von DOM an beliebigen Stellen Ihrer Seite einfügen. Der Vorteil liegt auf der Hand: Anstatt bei jeder Serverinteraktion die komplette Seite neu laden zu müssen (obwohl sich oft nur ein sehr geringer Teil derselben ändert), können Sie einzelne Stellen gezielt mit aktualisierten Daten modifizieren. Das Ziel sind dabei Webanwendungen, die sich so geschmeidig wie Desktopanwendungen präsentieren, anstatt zwischendurch immer wieder leere Seiten und eine Warte-Uhr anzuzeigen.
Der Schöpfer des Begriffs Ajax, der Webdesigner Jesse James Garret, behauptet, der Name sei kein Akronym für Asynchronous JavaScript And XML – und doch beschreibt diese Langform recht genau, um was es dabei geht:
- Asynchrone HTTP-Anfragen – Sie finden hinter den Kulissen statt, während die Besucher auf der Seite weiterlesen, ein Formular ausfüllen oder ähnliche Tätigkeiten durchführen. Eine Callback-Funktion liest die Serverantwort, sobald sie vorliegt.
- JavaScript – das benötigte HTTP-Anfrageobjekt gehört (unter verschiedenen Namen) zur JavaScript-Bibliothek aller aktuellen Browser.
- XML – die Antwort des Webservers kann (muss aber nicht) verschachteltes XML sein, das Sie ebenso per DOM verarbeiten können wie die Webseite selbst.
19.8.1 Die erste Ajax-Anwendung
Als erste kleine Testanwendung soll jeder Klick auf einen Link ein zufälliges Zitat eines PHP-Skripts anfordern. Dieses Zitat wird an einer bestimmten Stelle der – ansonsten gleichbleibenden – Seite eingetragen.
Ein Ajax-Anfrageobjekt erzeugen
Damit Ihre JavaScript-Anwendungen eigenständige HTTP-Anfragen durchführen können, benötigen Sie zuerst ein Anfrageobjekt. Ist dieses Objekt erst einmal erstellt, sind seine Eigenschaften und Methoden immer dieselben. Zuvor gibt es aber zunächst das Problem, dass die zu instanziierende Klasse je nach Browser drei verschiedene Namen besitzen kann – einen für Firefox, Safari, Chrome und so weiter und zwei andere für unterschiedlich aktuelle Internet-Explorer-Versionen.
Der folgende Codeblock verwendet try/catch-Blöcke, um die jeweils passende Klasse automatisch zu wählen oder eine Fehlermeldung auszugeben, falls Ajax-Anwendungen im vorliegenden Browser gar nicht möglich sind:
var anfrage = null;
try {
anfrage = new XMLHttpRequest();
} catch (err_ff) {
try {
anfrage = new ActiveXObject("Msxml2.XMLHTTP");
} catch (err_ms1) {
try {
anfrage = new ActiveXObject("Microsoft.XMLHTTP");
} catch (err_all) {
anfrage = null;
}
}
}
if (anfrage == null) {
alert ("Sie verwenden einen nicht Ajax-fähigen Browser.");
}
In den meisten Fällen ist es am praktischsten, diesen Code statisch (außerhalb einer Funktion) in einen JavaScript-Block im Head der Seite zu setzen. Die Alternative wäre eine entsprechende Funktion in einer .js-Datei, die ein fertiges Anfrageobjekt zurückgibt.
Die Ajax-Anfrage vorbereiten und versenden
Um eine Ajax-Anfrage abzuschicken, werden die Methoden open() und send() des Anfrageobjekts verwendet. open() benötigt drei Argumente:
- Die Anfrage-Methode, "POST" oder "GET" – beachten Sie für den Unterschied die Diskussion in Kapitel 17, »Webseitenerstellung mit (X)HTML und CSS«.
- Die Anfrage-URL – in aller Regel die URL eines serverseitigen Skripts. Beachten Sie, dass das entsprechende Skript kein vollständiges HTML-Dokument als Antwort erzeugen soll, sondern lediglich den relevanten Datenanteil. Mögliche Formate sind einfacher Text, diverse XML-Formate oder JSON (siehe Abschnitt 19.8.2, »Datenaustauschformate: XML und JSON«).
- Asynchronität der Anfrage (true oder false) – wenn Sie diesen letzten Parameter auf true setzen, wird die Anfrage asynchron versandt, sodass Sie weiterarbeiten können, während der Browser auf die Antwort wartet. false versendet sie dagegen synchron, und Sie müssen auf die Antwort warten wie auf das Neuladen einer ganzen Seite. Letzteres scheint zwar dem Sinn von Ajax zu widersprechen, ist aber in manchen Fällen nützlich, etwa um ein Formular nach der vollständigen Eingabe komplett zu überprüfen.
Für das Zitate-Beispiel muss als Erstes die URL definiert werden, an die die Anfrage gesendet wird. Das PHP-Skript soll zitat.php heißen und im selben Webserververzeichnis liegen wie die HTML/JavaScript-Datei selbst. Ein zuvor bereits angesprochenes Problem ist, dass manche Browser die Antwort auf eine GET-Anfrage im Cache speichern, sodass der Inhalt ab dem zweiten Aufruf nicht mehr geändert wird. Die folgende Zeile hängt die aktuelle Zeit (in Sekunden seit EPOCH) als Dummy-Parameter an die eigentliche URL an, sodass formaljuristisch gesehen jedes Mal eine neue URL entsteht:
var url = "zitat.php?dummy=" + new Date().getTime();
Der open()-Aufruf selbst sieht daraufhin so aus:
anfrage.open ("GET", url, true);
Es wird also die Anfrage-Methode GET verwendet, und der Parameter true macht die Anfrage asynchron.
Als Nächstes müssen Sie die Eigenschaft onreadystatechange des Anfrageobjekts setzen. Ihr Wert ist der Name einer Callback-Funktion (siehe Kapitel 10, »Konzepte der Programmierung«), die bei jeder Änderung des Bereitschaftszustands (Eigenschaft readyState) Ihres Anfrageobjekts aufgerufen wird. Es gibt insgesamt fünf Bereitschaftszustände, die anzeigen, wie weit die Bearbeitung der Anfrage bereits fortgeschritten ist:
- 0 – Anfrage wird nicht bearbeitet.
- 1 – Verbindung wurde aufgebaut.
- 2 – Anfrage wurde vollständig versandt.
- 3 – Empfang der Antwort beginnt.
- 4 – Antwort liegt vollständig vor.
Bei etwa 99 % aller Ajax-Anwendungen kümmert sich die Callback-Funktion aus nachvollziehbaren Gründen nur um den Zustand 4.
Die (als Nächstes besprochene) Callback-Funktion für das Zitate-Beispiel heißt hole-Zitat(), sodass die betreffende Zeile so aussieht:
anfrage.onreadystatechange = holeZitat;
Beachten Sie, dass hinter dem Funktionsnamen des Callbacks an dieser Stelle keine Parameterklammern stehen dürfen.
Zum Schluss wird die Anfrage versendet. Die Methode send() besitzt ein Argument, das den Body der HTTP-Anfrage repräsentiert. Da GET-Anfragen im Gegensatz zu POST keinen Body haben, ist der Wert im vorliegenden Fall null:
anfrage.send (null);
Alle genannten Codezeilen stehen in einer Funktion namens tauscheZitat(), die per Klick auf den folgenden Hyperlink aufgerufen wird:
<a href="#" onclick="tauscheZitat();">Zitat wechseln</a>
Die gesamte Funktion tauscheZitat() sieht somit wie folgt aus:
function tauscheZitat() {
// URL mit Zeit als Cache-Schutz kombinieren
var url = "zitat.php?dummy=" + new Date().getTime();
// Anfrage eröffnen: Methode GET, URL, asynchron
anfrage.open ("GET", url, true);
// Callback-Funktion für Zustandswechsel festlegen
anfrage.onreadystatechange = holeZitat;
// Anfrage senden (mit leerem Body, da GET)
anfrage.send (null);
}
Die Serverantwort verarbeiten
Wie bereits erwähnt, wird die Callback-Funktion holeZitat() bei jedem Wechsel des Bereitschaftszustands aufgerufen. Daher muss sie zuerst überprüfen, ob die Anfrage fertig bearbeitet wurde (readyState 4). Dies reicht allerdings nicht als Kriterium für eine qualifizierte Antwort – zusätzlich muss der HTTP-Statuscode (Eigenschaft status des Anfrageobjekts) anzeigen, dass sie brauchbar ist. Wie Sie bereits aus Kapitel 13, »Server für Webanwendungen«, wissen, lautet der Statuscode für eine gültige Ressource 200. Das Auslesen der Antwort wird mit anderen Worten durch folgende Prüfung umschlossen:
if (anfrage.readyState == 4) {
if (anfrage.status == 200) {
// Antworttext auslesen und eintragen
// ...
} else {
alert ("Fehlerhafte Server-Antwort: " +
anfrage.status);
}
}
Wie Sie sehen, wird für den Fall, dass der Status nicht 200 ist, eine Fehlermeldung ausgegeben. Bei einem anderen readyState als 4 geschieht dagegen gar nichts, weil alle anderen Bereitschaftszustände irrelevant sind.
Nun fehlen noch die Zeilen zum Auslesen der eigentlichen Serverantwort und zum Ändern des Zitatblocks auf der Webseite. Da die Antwort kein XML, sondern reiner Text ist, können Sie sie fix und fertig aus der Eigenschaft responseText des Anfrageobjekts auslesen:
var antwort = anfrage.responseText;
Das Zitat steht im HTML-Dokument in einem <div> namens zitat; anfangs enthält es nur ein geschütztes Leerzeichen:
<div id="zitat" style="color: #FF0000"> </div>
Um das aus dem PHP-Skript gelesene Zitat an dieser Stelle einzutragen, genügt es, den einzigen (und damit unter anderem ersten) Kindknoten dieses <div>-Elements auf den Wert der Variablen-Antwort zu setzen:
document.getElementById("zitat").firstChild.nodeValue =
antwort;
Hier noch einmal die gesamte Funktion holeZitat():
function holeZitat() {
// Nur aktiv werden, wenn Bereitschaftszustand 4
if (anfrage.readyState == 4) {
// Gültige Antwort (Status 200)?
if (anfrage.status == 200) {
// Text der Server-Antwort auslesen
var antwort = anfrage.responseText;
// Zitat in das Dokument einfügen
document.getElementById("zitat").firstChild.
nodeValue = antwort;
} else {
// Ungültige Antwort
alert ("Fehlerhafte Server-Antwort: " +
anfrage.status);
}
}
}
Das PHP-Skript
Selbstverständlich können Sie jede beliebige serverseitige Technologie verwenden, um die Skripte oder Programme zu schreiben, die die Antworten auf die Ajax-Anfragen liefern. In diesem Buch wird PHP verwendet, weil diese Sprache bereits im vorigen Kapitel eingeführt wurde. Das folgende kurze Skript müssten Sie daher auch problemlos verstehen. Die einzige Besonderheit besteht darin, dass seine Ausgabe kein vollständiges HTML-Dokument, sondern nur das reine, jeweils ausgewählte Zufallszitat ist:
<?php
// Array mit allen Zitaten erzeugen
$zitate = array (
"\"To be is to do.\" -- Socrates",
"\"To do is to be.\" -- Sartre",
"\"Do be do be do.\" -- Sinatra"
);
// Ein zufälliges Zitat auswählen
$zitat = $zitate[array_rand($zitate)];
// Das Zitat ausgeben
echo $zitat;
?>
Speichern Sie dieses Skript unter dem Namen zitat.php in einem Site-Verzeichnis Ihres Webservers. Im gleichen Verzeichnis wird auch die nachfolgend vollständig abgedruckte HTML/JavaScript-Datei gespeichert.
Die vollständige Ajax-HTML-Datei
Der Vollständigkeit halber wird hier noch einmal die gesamte HTML-Datei mit dem ausführlich kommentierten Ajax-JavaScript-Code abgedruckt. Speichern Sie diese Datei im gleichen Verzeichnis wie das PHP-Skript. Anschließend können Sie sie über Ihren lokalen Webserver im Browser aufrufen. Sobald Sie den Link Zitat wechseln anklicken, wird ein anderes Zitat nachgeladen (da es nur drei verschiedene gibt, bemerken Sie das allerdings möglicherweise nicht jedes Mal; in der Praxis würde man besser ein Zufallszitat aus einer Datenbank laden).
<html>
<head>
<title>Kleiner Ajax-Test</title>
<script language="JavaScript" type="text/javascript">
<!--
// Ajax-Anfrageobjekt erzeugen
// Zunächst die Referenzvariable deklarieren
var anfrage = null;
try {
// Klasse für Firefox, Opera & Co.
anfrage = new XMLHttpRequest();
} catch (err_ff) {
try {
// Klasse für neuere IEs
anfrage = new ActiveXObject("Msxml2.XMLHTTP");
} catch (err_ms1) {
try {
// Klasse für ältere IEs
anfrage = new ActiveXObject("Microsoft.XMLHTTP");
} catch (err_all) {
// Inkompatibler Browser -- keine Klasse passt
anfrage = null;
}
}
}
if (anfrage == null) {
// Fehlermeldung, falls kein Anfrage-Objekt erzeugt
alert ("Sie verwenden einen nicht Ajax-fähigen Browser.");
}
// tauscheZitat(): Wird bei Klick auf einen Link
// aufgerufen; lädt neues Zitat per Ajax-Anfrage
function tauscheZitat() {
// URL mit Zeit als Cache-Schutz kombinieren
var url = "zitat.php?dummy=" + new Date().getTime();
// Anfrage eröffnen: Methode GET, URL, asynchron
anfrage.open ("GET", url, true);
// Callback-Funktion für Zustandswechsel festlegen
anfrage.onreadystatechange = holeZitat;
// Anfrage senden (mit leerem Body, da GET)
anfrage.send (null);
}
// holeZitat(): Callback-Funktion, die nach
// Ajax-Anfrage die Server-Antwort ausliest und anzeigt
function holeZitat() {
// Nur aktiv werden, wenn Bereitschaftszustand 4
if (anfrage.readyState == 4) {
// Gültige Antwort (Status 200)?
if (anfrage.status == 200) {
// Text der Server-Antwort auslesen
var antwort = anfrage.responseText;
// Zitat in das Dokument einfügen
document.getElementById("zitat").firstChild.
nodeValue = antwort;
} else {
// Ungültige Antwort
alert ("Fehlerhafte Server-Antwort: " +
anfrage.status);
}
}
}
//-->
</script>
</head>
<body>
Hier steht ein dynamisch austauschbares Zufallszitat:
<div id="zitat" style="color: #FF0000"> </div>
<a href="#" onclick="tauscheZitat();">Zitat wechseln</a>
</body>
</html>
19.8.2 Datenaustauschformate: XML und JSON
Das Beispiel mit den Zitaten zeigte bereits den Ablauf einer asynchronen Anfrage und die Zusammenarbeit mit einem serverseitigen Skript, das kein komplettes Dokument zurückliefert, sondern nur Einzelinhalte zum Austauschen. Es handelte sich allerdings nur um einfachen Text – was übrigens trotz des »x« im Namen Ajax recht häufig vorkommt. Falls das Server-Skript aber mehrere Daten liefern soll, brauchen Sie irgendein Format, um diese zu organisieren.
Natürlich steht es Ihnen frei, einen String mit beliebigen Trennzeichen zusammenzubasteln und dann per JavaScript »zu Fuß« zu parsen. Üblich ist das aber nicht, und empfehlenswert auch nicht; die meisten Entwickler verwenden eines der folgenden beiden Formate:
- XML – diese Auszeichnungssprache haben Sie bereits in Kapitel 15, »XML«, kennengelernt. Falls das Format der Daten XML ist, stehen sie in der Eigenschaft responseXML des Ajax-Anfrageobjekts bereit. Ihr Inhalt ist ein DOM-Baum, den Sie nach den bekannten Regeln zerlegen können.
- JSON (JavaScript Object Notation) – die Daten werden als beliebig tief verschachteltes JavaScript-Array geliefert. Da es sich nicht um XML handelt, werden sie aus der Eigenschaft responseText gelesen und anschließend per eval() von einem String in ein JavaScript-Objekt umgewandelt. Für die meisten Serversprachen gibt es Bibliotheken, die JSON-Daten aus den nativen Datenformaten dieser Sprachen erzeugen. Im weiteren Verlauf des Kapitels lernen Sie beispielsweise die JSON-Funktionen von PHP kennen.
Ob Sie sich letztlich für XML, für JSON oder doch für ein eigenes Format entscheiden, ist Geschmackssache. Es folgt ein etwas umfangreicheres Praxisbeispiel, das mit beiden Standardformaten gezeigt wird.
19.8.3 Größeres Beispiel: eine interaktive Länderliste
Eine der ersten Ajax-Anwendungen, die einem größeren Publikum bekannt wurden, heißt Google Suggest. Bereits während der Eingabe eines Suchbegriffs werden Vorschläge eingeblendet, sortiert nach der Anzahl der Suchtreffer. Die hier beschriebene Beispielanwendung verwendet eine ähnliche Technik, um eine Liste aller Staaten der Welt nach jedem Tastendruck anhand der bisherigen Eingabe zu filtern. Zusätzlich erscheint jeder Name als Hyperlink. Bei einem Rollover mit der Maus wird an der Mausposition ein Layer eingeblendet, der die dynamisch vom Server nachgeladene Hauptstadt anzeigt.
Die Länderliste liegt in einer MySQL-Datenbanktabelle namens laender, die sich wiederum innerhalb einer Datenbank namens welt befindet und folgendes Schema aufweist:
id | name | hauptstadt
-----+-----------------+-----------------
L1 | Afghanistan | Kabul
L2 | Ägypten | Kairo
... | ... | ...
L194 | Zypern | Nikosia
Die id ist absichtlich keine Zahl, sondern ein jeweils mit »L« beginnender String, denn aus der Liste soll ein assoziatives und kein numerisches Array erstellt werden. Diese Daten werden in einem PHP-Skript aus der Datenbank gelesen und anschließend an das JavaScript geschickt – in der ersten Fassung im XML-Format und danach als JSON-Objekt. An dieser Stelle betreibt die Clientseite der Anwendung ihr eigenes »Caching«: Die gesamte Liste wird nur einmal heruntergeladen und in einem Array gespeichert.
Die Erzeugung des Ajax-Anfrageobjekts wird in eine Datei namens ajax.js ausgelagert und ist identisch mit dem Code aus dem vorigen Beispiel. Das gilt im Grunde für jede Ajax-Anfrage – es sei denn, Sie brauchen einmal mehrere Anfrageobjekte; in diesem Fall können Sie eine Funktion schreiben, die nach dem gezeigten Schema ein solches Objekt erzeugt und zurückgibt, und sie danach beliebig oft aufrufen.
Das PHP-Skript
Das kurze PHP-Skript laender.php, das die Daten im XML-Format liefert, sieht wie folgt aus:
<?php
// Datenbank-Parameter (anpassen)
$host = "localhost";
$user = "dbuser";
$pass = "Ihr Passwort";
$db = "welt";
// Datenbankverbindung (mysqli)
$conn = new mysqli($host, $user, $pass, $db);
// Dokumentbeginn: XML-PI, öffnendes <laender>-Tag
$laender = "<?xml version=\"1.0\"
encoding=\"iso-8859-1\" ?>";
$laender .= "<laender>";
// SQL-Abfrage
$sql = "SELECT id, name FROM laender
ORDER BY name ASC";
$query = $conn->query ($sql);
// Daten aus der Datenbank lesen
while (list ($id, $land) = $query->fetch_row()) {
// als <land>-Element in den XML-Code schreiben
$eintrag =
"<land><id>$id</id><name>$land</name></land>";
$laender .= $eintrag;
}
// Schließendes </laender>-Tag
$laender .= "</laender>";
// MIME-Type und Dokument ausgeben
header ("Content-type: text/xml");
echo ($laender);
?>
Hier passiert so weit noch nichts Unbekanntes. Natürlich gibt es auch für PHP geeignete Bibliotheken, die aus einem DOM-Baum sauberen XML-Code erzeugen können. Die vorliegende XML-Struktur ist allerdings sehr einfach, sodass es genügt, sie als String aufzubauen. Die Ausgabe ist das folgende XML-Dokument:
<?xml version="1.0" encoding="iso-8859-1" ?>
<laender>
<land><id>L1</id><name>Afghanistan</name></land>
<land><id>L2</id><name>Ägypten</name></land>
...
<land><id>L194</id><name>Zypern</name></land>
Das JavaScript zur Verarbeitung der Länderdaten
Der JavaScript-Code für die eigentliche Anwendung wurde ebenfalls ausgelagert, und zwar in die Datei laender.js. Ihre Methode liesStaaten(), die durch den Event Handler onLoad des HTML-Dokuments aufgerufen wird, sorgt für die Anfrage an laender.php. Hier der Rumpf dieser Methode:
var url = "laender.php";
anfrage.open ("GET", url, false);
anfrage.onreadystatechange = holeListe;
anfrage.send (null);
Das dritte Argument von open() lautet diesmal false – es handelt sich also um eine synchrone Anfrage, die blockiert, bis die Antwort vorliegt (genauer gesagt bis zum Wechsel von readyState). Dieses Verfahren bietet sich hier an, weil die Anwendung erst nutzbar ist, nachdem die Länderliste geladen wurde.
Für das Einlesen der Länderliste ist die Funktion holeListe() zuständig. Nachdem sie wie üblich sichergestellt hat, dass der readyState 4 und der status 200 ist, liest sie das Ergebnis aus der Eigenschaft responseXML des Anfrageobjekts:
var laenderXML = anfrage.responseXML;
Es handelt sich, wie bereits erwähnt, um einen DOM-Baum. Aus diesem werden mithilfe bereits bekannter DOM-Methoden die ID-Name-Paare ausgelesen und in ein verschachteltes Array gepackt (die Array-Variable staaten wurde zuvor als global angelegt):
var laenderXML = anfrage.responseXML;
var laender = laenderXML.getElementsByTagName("land");
for (i = 0; i < laender.length; i++) {
var id = laender[i].getElementsByTagName("id")[0].
firstChild.nodeValue;
var land = laender[i].getElementsByTagName("name")[0].
firstChild.nodeValue;
var eintrag = new Array(id, land);
staaten.push (eintrag);
}
Anschließend wird die Methode filterListe() aufgerufen. Sie zeigt alle Elemente der Liste an, die dem aktuellen Filterkriterium entsprechen. Dies ist zu Beginn der leere String, sodass alle Staaten angezeigt werden. Die HTML-Seite enthält ein Textfeld, das nach jedem Tastendruck ebenfalls filterListe() aufruft:
<input type="text" name="eingabe"
onkeyup="filterListe();" />
Den Code von filterListe() sehen Sie im Folgenden im Komplett-Listing. Die wesentlichen Arbeitsschritte sind das Löschen der bisherigen Anzeige durch Aufruf von loeschListe() und die Untersuchung aller Elemente des Arrays staaten in einer Schleife, wobei alle zum aktuellen Filter passenden Ländernamen ausgegeben werden. Dabei werden auch die Event Handler onmouseup und onmousedown erzeugt.
Sobald ein Ländername mit der Maus berührt wird, aktiviert dies die Funktion zeigeHauptstadt(). Diese speichert zunächst die Position für den anzuzeigenden Tooltip: die aktuelle Mausposition, um je 10 Pixel nach rechts und nach unten verschoben. Das Auslesen der Mauskoordinaten funktioniert im Internet Explorer anders als in Mozilla-Browsern:[Anm.: document.all ist ein früher DOM-Vorfahr des Internet Explorers und wird hier nur verwendet, um diesen Browser von allen anderen zu unterscheiden. Es kann nicht garantiert werden, dass künftige IE-Versionen dies weiter unterstützen werden, sodass ein Test des navigator-Objekts sicherer ist.]
mausX = document.all ? window.event.offsetX + 10 :
e.pageX + 10;
mausY = document.all ? window.event.offsetY + 10 :
e.pageY + 10;
Anschließend wird die ID für die Datenbankabfrage aus der URL des berührten Links gelesen:
var id = this.toString();
id = id.substring (id.indexOf ("#") + 1);
Die ID wird per asynchroner GET-Anfrage an das Skript hauptstadt.php gesendet. Es besteht aus folgenden Zeilen:
<?php
// (Datenbankparameter)
// ...
// Datenbankverbindung
$conn = new mysqli ($host, $root, $pass, $db);
// ID aus der URL ermitteln
$id = $_GET['id'];
// SQL-Abfrage senden
$sql = "SELECT name, hauptstadt FROM laender
WHERE id=\"$id\"";
$query = $conn->query ($sql);
// Land und Hauptstadt lesen
list ($land, $hauptstadt) = $query->fetch_row();
// Datentyp und -inhalt ausgeben
header ("Content-type: text/plain;
charset=iso-8859-1");
echo ("$land: $hauptstadt");
?>
Wie Sie sehen, besteht die Antwort aus einem String in der Form »Land: Hauptstadt«. Sobald die Antwort eintrifft, wird die JavaScript-Callback-Methode holeHauptstadt() aktiv. Sie blendet den Tooltip mit dem neuen Text an der zuvor berechneten Position ein.
Hier der gesamte Code des Skripts laender.js:
// Array für die Staaten vorbereiten
var staaten = new Array();
// Globale Variablen für Mauspositionen
var mausX = null;
var mausY = null;
// Wartezustand
var warten = true;
// loescheListe(): Bisherige Anzeige löschen
function loescheListe() {
liste = document.getElementById ("laenderliste");
while (liste.hasChildNodes()) {
liste.removeChild (liste.firstChild);
}
}
//loescheFilter(): Eingabefeld leeren; alles ausgeben
function loescheFilter() {
document.filter.eingabe.value = "";
document.filter.eingabe.focus();
filterListe();
}
// liesStaaten(): Ajax-Anfrage zu Beginn, um die Liste
// aller Staaten auszulesen
function liesStaaten() {
// URL des PHP-Skripts für die Staatenliste
var url = "laender.php";
// Anfrage eröffnen: Methode GET, URL, synchron(!)
anfrage.open ("GET", url, false);
anfrage.onreadystatechange = holeListe;
anfrage.send (null);
}
// holeListe(): Liste der Staaten abholen und anzeigen
function holeListe() {
// Antwort angekommen?
if (anfrage.readyState == 4) {
// Antwort akzeptabel?
if (anfrage.status == 200) {
warten = false;
// Antwort speichern
var laenderXML = anfrage.responseXML;
// Über alle <land>-Tags im DOM-Baum iterieren
var laender =
laenderXML.getElementsByTagName("land");
for (i = 0; i < laender.length; i++) {
var id = laender[i].getElementsByTagName("id")
[0].firstChild.nodeValue;
var land = laender[i].getElementsByTagName
("name")[0].firstChild.nodeValue;
var eintrag = new Array(id, land);
staaten.push (eintrag);
}
// Anzeige löschen
loescheListe();
// Alle Staaten eintragen (leerer Filter!)
filterListe();
} else {
alert ("Staatenliste nicht verfuegbar!");
}
}
}
// zeigeHauptstadt(): Ajax-Anfrage zum Anzeigen der
// Hauptstadt
function zeigeHauptstadt(e) {
if (warten) {
return;
}
mausX = document.all ? window.event.offsetX + 10 :
e.pageX + 10;
mausY = document.all ? window.event.offsetY + 10 :
e.pageY + 10;
var id = this.toString();
id = id.substring (id.indexOf ("#") + 1);
var url = "hauptstadt.php?id=" + id;
anfrage.open ("GET", url, true);
anfrage.onreadystatechange = holeHauptstadt;
anfrage.send (null);
}
// holeHauptstadt(): Hauptstadt abholen und anzeigen
function holeHauptstadt() {
if (anfrage.readyState == 4 && anfrage.status == 200) {
var hs = anfrage.responseText;
var dieBox = document.getElementById ("hauptstadt");
dieBox.firstChild.nodeValue = hs;
dieBox.style["visibility"] = "visible";
dieBox.style["left"] = mausX + "px";
dieBox.style["top"] = mausY + "px";
}
}
// versteckeHauptstadt(): HS-Ebene verstecken
function versteckeHauptstadt() {
var dieBox = document.getElementById ("hauptstadt");
dieBox.style["visibility"] = "hidden";
}
// filterListe(): Liste anhand der Texteingabe filtern
function filterListe() {
// Bisherigen Text entfernen
loescheListe();
// Suchfilter lesen
var fText = document.filter.eingabe.value;
// Alle Staaten eintragen, auf die der Filter passt
for (i in staaten) {
var eintrag = staaten[i];
var id = eintrag[0];
var land = eintrag[1];
if (land.toLowerCase().indexOf
(fText.toLowerCase()) >= 0) {
var aNode = document.createElement ("a");
var href = document.createAttribute ("href");
href.nodeValue = "#" + id;
aNode.setAttributeNode (href);
aNode.onmouseover = zeigeHauptstadt;
aNode.onmouseout = versteckeHauptstadt;
var tNode = document.createTextNode (land);
var bNode = document.createElement ("br");
aNode.appendChild (tNode);
liste.appendChild (aNode);
liste.appendChild (bNode);
}
}
}
Aus Gründen der Vollständigkeit sehen Sie hier noch die eigentliche Seite, laenderfilter.html:
<html>
<head>
<title>Ajax-Länderfilter</title>
<meta http-equiv="Content-type" content="text/html; charset=iso-8859-1"
/>
<script language="JavaScript" type="text/javascript" src="ajax.js">
</script>
<script language="JavaScript" type="text/javascript" src="laender.js">
</script>
</head>
<body onload="liesStaaten();">
<h1>Alle Staaten der Erde</h1>
<form name="filter">
Suchfilter:
<input type="text" name="eingabe" onkeyup="filterListe();" />
<input type="button" value="Zurücksetzen"
onclick="loescheFilter();" />
</form>
<div id="laenderliste">
Bitte warten ...
</div>
<span id="hauptstadt" style="position: absolute; top: 0px; left: 0px;
visibility:hidden; background-color: #00FFFF"> </span>
</body>
</html>
Abbildung 19.2 zeigt das Skript im Einsatz.
Abbildung 19.2 Der Ajax-Länderfilter nach Eingabe von »de«
JSON-Version
Die JSON-Version der Länderliste wird zunächst als verschachteltes PHP-Array erzeugt. Anschließend kommt die Funktion json_encode()[Anm.: Seit PHP 5.2.0 sind die JSON-Funktionen fest eingebaut; für ältere Versionen stehen sie als PECL-Erweiterung zur Verfügung.] zum Einsatz, um ein äquivalentes JavaScript-Konstrukt zu erzeugen. Unglücklicherweise kann diese Funktion nur mit UTF-8-Zeichen umgehen, sodass Sie die ISO-Latin-1-Strings aus der Datenbank zunächst umwandeln müssen; dies erledigt die PHP-Funktion utf8_encode(). Hier der vollständige Quellcode von laender.php in der geänderten Fassung:
<?php
// (Datenbankparameter)
// ...
// Datenbankverbindung
$conn = new mysqli($host, $root, $pass, $db);
$laender = array();
// Datenbankabfrage
$sql = "SELECT id, name FROM laender
ORDER BY name ASC";
$query = $conn->query ($sql);
while (list ($id, $land) = $query->fetch_row()) {
// Daten in UTF-8 konvertieren und in Array packen
$eintrag = array(utf8_encode($id),
utf8_encode($land));
array_push($laender, $eintrag);
}
// JSON-Code erzeugen
$ausgabe = json_encode($laender);
// Ausgabe
header ("Content-type: text/plain; charset=utf-8");
echo ($ausgabe);
?>
Die erzeugten Daten haben das folgende Format:
[["L1","Afghanistan"],["L2","Ägypten"],...,
["L194","Zypern"]]
Es handelt sich also bereits um das fertige JavaScript-Array. Dies vereinfacht die Funktion holeListe() enorm; sie kann sich den DOM-Aufwand sparen und braucht das Array nur noch mithilfe von eval() auszupacken:
staaten = eval (anfrage.responseText);
Beachten Sie zuletzt, dass auch bei der zugehörigen Variante von hauptstadt.php die UTF-8-Konvertierung erfolgen muss. Außerdem braucht diese Fassung von laenderfilter.html einen HTTP-Header oder ein Meta-Tag für diesen Zeichensatz. Sie können beide Versionen mit allen zugehörigen Skripten auf der Website zum Buch herunterladen.
Die wichtigsten Ajax-Aufgaben, etwa die Erzeugung des Anfrageobjekts und die Manipulation von DOM-Bäumen, sind immer gleich. Deshalb können Sie sich die Arbeit erleichtern, indem Sie eines der vielen frei verfügbaren Ajax-Frameworks verwenden. Diese automatisieren diese Aufgaben mit unterschiedlichen Schwerpunkten. Bekannte Beispiele sind jQuery (http://jquery.com/), script.aculo.us (http://script.aculo.us/), Prototype (http://prototype.conio.net/) und dojo (http://dojotoolkit.org/). Downloads und Dokumentationen finden Sie auf den angegebenen Websites. Im nachfolgenden Kasten erfahren Sie das Wichtigste über jQuery.
jQuery im Schnellüberblick
Eine der praktischsten JavaScript- und Ajax-Bibliotheken ist jQuery. Der Zugriff auf beliebige DOM-Elemente erfordert viel weniger Schreibarbeit als bei selbst geschriebenem JavaScript, zahlreiche praktische Funktionen sind bereits eingebaut, und zudem gibt es diverse Plug-ins für zusätzliche Funktionalität.
Um jQuery zu verwenden, müssen Sie zunächst die Bibliothek laden; mit der aktuellen Version funktioniert dies wie folgt:
<script type="text/css" src="jquery-1.10.2.js"></script>
Die gesamte Bibliotheksdatei ist lediglich etwa 267 KByte groß, und es gibt sogar eine (durch Weglassen von Whitespace und einbuchstabige Bezeichner) minimalisierte Variante namens jquery-1.10.2.min.js, die mit ungefähr 91 KByte auskommt.
Der Zugriff auf die jQuery-Funktionalität erfolgt über eine globale Funktion namens jQuery, für die auch die praktische Abkürzung $ zur Verfügung steht.
Hier einige wichtige jQuery-Selektoren für den Zugriff auf Elemente des DOM-Baums im Überblick:
- $('Element') gilt für alle Elemente mit dem angegebenen Namen, entspricht also dem Array, das document.getElementsByTagName() zurückliefert. Beispiel: $('p') wählt alle Absätze im Dokument aus.
- $('Element:first'), $('Element:last') und $('Element:eq(Index)') greifen auf das erste, letzte beziehungsweise numerisch angegebene Element zurück, wobei der Index für :eq wie üblich bei 0 beginnt. Beispiel: $('form:eq(0)') steht für das erste Formular auf der Seite.
- $('Element > Kindelement') wählt direkte Kindelemente des angegebenen Elements aus; $('Element Nachfahre') kann auch auf beliebig tief unter dem Element verschachtelte Elemente zugreifen. In beiden Fällen sind beliebig tiefe Hierarchien möglich. Beispiel: $('form input') liefert alle <input>-Elemente unterhalb aller <form>-Elemente.
- $('#id') greift auf das Element mit dem angegebenen id-Attribut zu, genau wie document.getElementById(). Beispiel: $('#eingabe').
- $('.Klasse') wählt alle Elemente mit dem genannten class-Attribut aus. Beispiel: $('.message).
- $('[Attribut]') beziehungsweise $('Element[Attribut]') wählt alle Elemente aus, die das angegebene Attribut besitzen. Mit $('[Attribut=Wert']) lässt sich dies auch einen bestimmten Wert des Attributs eingrenzen. Beispiel: $('p[align=right]') liefert alle Absätze mit dem Attribut align="right".
Mit den Elementen, die über diese (und viele andere) Selektoren ausgewählt wurden, lassen sich zahlreiche Operationen durchführen. Hier nur einige wichtige:
- Selektor.text('neuer Text') ändert den in den gewählten Elementen ausgewählten Text in den angegebenen Wert. Beispiel: $('a#next').text('Nächste Seite') setzt den Text des Links mit der ID next auf »Nächste Seite«.
- Selektor.html('HTML-String') ersetzt den Inhalt der ausgewählten Elemente durch den angegebenen HTML-Code. Beispiel: $('#info').html('<p>Dies ist eine Information</p>') ersetzt den HTML-Inhalt des Elements mit der ID info durch den angegebenen Absatz.
- Selektor.append('HTML-String') und Selektor.prepend('HTML-String') fügen vor beziehungsweise nach dem Inhalt der ausgewählten Elemente beliebiges HTML ein. Beispiel: $('#info').append('<p>Mehr Informationen</p>') erweitert den Inhalt des Elements mit der ID info.
- Selektor.css('Eigenschaft', 'Wert') setzt die gewünschte CSS-Eigenschaft auf den gewählten Wert. Mit Selektor.css({'Eigenschaft': 'Wert', ...}) lassen sich auch beliebig viele Eigenschaften ändern. Beispiel: $('td.info').css ('{'color': '#FF0000', 'font-weight': 'bold'}) setzt den Text in <td>-Elementen der Klasse info rot und fett.
- Selektor.addClass('Klasse') und Selektor.removeClass('Klasse') bieten eine schnellere Möglichkeit, CSS-Eigenschaften zu ändern: Die angegebene CSS-Klassendefinition wird hinzugefügt beziehungsweise entfernt.
Für Event Handler bietet jQuery ebenfalls einen praktischen Ersatz; Sie brauchen dafür keinen JavaScript-Code mehr in HTML-Attribute zu schreiben, was Ihre Webseiten erheblich übersichtlicher macht. Im Allgemeinen wird einem Ereignis für einen Selektor eine anonyme Funktion zugewiesen; die allgemeine Form sieht also folgendermaßen aus:
Selektor.Event(function() {
// Ereignisbehandlung
})
Wichtige Events sind unter anderem click (Anklicken des ausgewählten Elements), focus (Element erhält den Eingabefokus), blur (Element verliert den Eingabefokus), change (Änderung des Elementinhalts) oder hover (Mausberührung). Wichtig: hover nimmt zwei durch Komma getrennte anonyme Funktionen entgegen, die erste für die Mausberührung und die zweite für das Verlassen. Interessant ist auch das Event toggle, dem Sie beliebig viele anonyme Funktionen zuweisen können. Bei jedem Klick wird reihum die jeweils nächste von ihnen aufgerufen.
Das folgende Beispiel fügt <tr>-Elementen, das heißt Tabellenzeilen, beim Berühren die CSS-Klasse betonung hinzu und entfernt sie beim Verlassen wieder – ideal, damit User sich in umfangreichen Tabellen besser zurechtfinden können:
$('tr').hover(
function() {
$(this).addClass('betonung');
},
function() {
$(this).removeClass('betonung');
}
);
Auch Ajax-Anfragen lassen sich mit jQuery sehr leicht durchführen. Mithilfe der Funktion $.ajax(Optionen) wird die Ajax-Anfrage gesendet. Die verschiedenen Option: Wert-Paare stellen unter anderem die Parameter der Anfrage und die Callback-Funktion bereit. Die wichtigsten Parameter sind dabei url (die URL der Ajax-Anfrage), type (Versandmethode 'GET' oder 'POST'), async (asynchrone Anfrage, true oder false), data (zu sendende Daten im Format {'name': 'Wert', ...}) und success (durchzuführende Änderungen bei Erfolg der Ajax-Anfrage, meist als anonyme Funktion).
Das folgende Beispiel sendet eine asynchrone GET-Anfrage an ein PHP-Skript namens ajaxhelper.php, geht davon aus, dass das Ergebnis reiner Text ist, und schreibt diesen in ein Element mit der ID message:
$.ajax(
type: 'GET',
url: 'ajaxhelper.php',
async: true,
success: function(msg) {
$('#message').text(msg);
}
);
Falls das Ergebnis XML ist, können Sie es mithilfe der zuvor beschriebenen jQuery-Selektoren durchsuchen und beispielsweise über text() den enthaltenen Text auslesen. Dazu wird der Name der XML-Ergebnisvariablen durch Komma getrennt hinter den eigentlichen Selektor geschrieben. Das folgende Beispiel liest den Text für das Element mit der ID message aus einem XML-Element namens <message>:
$.ajax(
type: 'GET',
url: 'ajaxhelper.php',
async: true,
success: function(xmlResponse) {
$('#message').text($('message', xmlResponse).text());
}
);
Dies war natürlich nur ein allererster Einblick in die vielfältigen Möglichkeiten von jQuery. Ausführliche Informationen und viele hilfreiche Beispiele finden Sie in der Originaldokumentation unter http://docs.jquery.com/.
Ihr Kommentar
Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.