Anzeige
Tutorialbeschreibung

Javascipt und Ajax - AJAX

Javascipt und Ajax - AJAX

In diesem Kapitel wird die AJAX etwas näher gebracht. Du bekommst erste Einblicke und erfährst die Entstehungsgeschichte.


Das nun folgende Tutorial ist ein Auszug aus dem Buch: Professionelle Websites von Stefan Münz.
Die Veröffentlichung des Kapitels erfolgt mit freundlicher Genehmigung von Pearson Education.

Kapitel 18 AJAX

Die Metapher vom alten Wein in neuen Schläuchen wurde beim Thema AJAX schon mehr als überstrapaziert, aber sie trifft einfach perfekt zu. Das ging übrigens auch diesem Buch so: Das Thema AJAX wurde dort (in einer Vorauflage) bereits behandelt, bevor es den Begriff gab.

Wie ist das möglich? Nun, AJAX ist – genauso wie DHTML – gar keine Technologie, sondern ein Marketing-Begriff. Er stammt von Jesse J. Garrett, Berater bei der amerikanischen Firma AdaptivePath. Unter http://www.adaptivepath.com/publications/essays/archives/000385.php veröffentlichte er im Februar 2005 einen Artikel, in dem er eigentlich nichts Bahnbrechendes erzählte. Eine Neuigkeit gab es in dem Artikel allerdings schon: den Begriff AJAX. Garrett zufolge steht das für Asynchronous JavaScript + XML. Eine sehr merkwürdige Abkürzung, denn das zweite A steht für das Plus – englisch And. Und auch von der fachlichen Seite her kann man über fast jeden Buchstaben in AJAX debattieren: Asynchron ist das Ganze nicht notwendigerweise, XML wird fast nie eingesetzt, lediglich das J für JavaScript ist passend. Einige Zeit später hat auch Garrett das eingesehen und verwendet mittlerweile die gemischte Schreibweise: Ajax. Außerdem betont er, Ajax/AJAX sei kein Akronym (mehr), sondern einfach ein Begriff.

Und genau dieser letzte Punkt ist es, der eine Bewegung entfacht hat, die mit Garretts Artikel ihren Anfang hatte. Der Artikel enthält in der Tat überhaupt nichts Neues, aber es gibt endlich einen Begriff für den Technologiemix, der AJAX ausmacht, einen Begriff, den auch technisch nicht versierte Personen problemlos verwenden können. Und obwohl es schon vor Garrett viele AJAX-Anwendungen gab – seit Garretts Artikel gibt es einen Oberbegriff, und es vergeht kaum ein Tag, an dem es nicht irgendeine Ankündigung gibt, eine Software wäre jetzt »AJAX-fähig«. Das ist zwar manchmal eine Mogelpackung (aufgrund der sehr schwammigen Definition von AJAX), aber zeigt sehr gut, wohin der Web-Trend geht.


18.1 AJAX-Beispiele 
topBilder
topBilder

Als einer der Vorreiter in der AJAX-Technologie wird Google genannt. Zwar ist Google beileibe nicht die erste Firma, die auf AJAX gesetzt hat, aber sie ist eine der bekanntesten. Gern wird Google Suggest (http://www.google.com/webhp?complete=1&hl=en) zitiert: Nach Eingabe von ein paar Zeichen erscheinen wie von Geisterhand ein paar Vorschläge, wonach Sie eventuell suchen möchten – inklusive einer ungefähren (und in Wahrheit recht unpräzisen) Schätzung, wie viele Treffer diese Suchanfrage zurückliefern würde.

Bilder

Abbildung 18.1     Google Suggest

Aber es gibt noch zahlreiche weitere AJAX-Beispiele:

gpBilder
 
http://demo.neximage.com/ – eine Bildbearbeitung (!) auf AJAX-Basis
gpBilder
 
http://www.writely.com/ – eine Textverarbeitung (!) auf AJAX-Basis
gpBilder
 
http://spreadsheets.google.com/ und http://www.numsum.com/ und http://ajaxxls.com/ – drei Tabellenkalkulationen (!) auf AJAX-Basis

Davon abgesehen setzen mittlerweile immer mehr Online-Dienste AJAX ein, mal mehr und mal weniger auffällig.

Bilder

Abbildung 18.2     Bildbearbeitung per AJAX

Bilder

Abbildung 18.3     Textverarbeitung per AJAX

Bilder

Abbildung 18.4     Tabellenkalkulation per AJAX

All diese Beispiele haben eins gemeinsam: Die Inhalte der Seite verändern sich, ohne dass die Seite neu geladen wird. Es gibt also keinen Page-Refresh. Klar, dass hier eine ganze Menge JavaScript mit im Spiel ist. Es ist aber nicht eine einzelne Wunderwaffe, die das ermöglicht, sondern ein kluger Mix aus alten und neuen Technologien und Aspekten der JavaScript-Entwicklung

 

 

18.2 AJAX-Technik 
topBilder

Der Ursprung von AJAX ist Microsoft. Zu Zeiten des Internet Explorer 5 hat das Internet-Explorer-Team eine Technologie in die Browser integriert, um im Hintergrund HTTP-Anfragen abzusetzen und die Rückgabe auszuwerten. Die Anforderung kam vom Office-Team, genauer gesagt von den Outlook-Entwicklern. Die Web-Schnittstelle von Outlook (OWA – Outlook Web Access) benötigte diese Funktionalität der HTTP-Anfragen im Hintergrund, um beispielsweise ohne permanentes Neuladen zu prüfen, ob es schon neue Mails gibt.

Dieses Feature – implementiert als ein ActiveX-Control namens XMLHttpRequest – fristete lange Zeit ein Schattendasein, denn wo außerhalb eines Intranets ist es überhaupt vertretbar, nur für einen Zielbrowser zu entwickeln?

Doch die Idee hinter XMLHttpRequest ist hervorragend, nur die Beschränkung auf ActiveX ist natürlich für einen systemunabhängigen Einsatz ein Unding. Deswegen haben die Hersteller der anderen Browser nachgelegt und XMLHttpRequest als natives Browserobjekt implementiert. Seit Mozilla 1.0, Netscape 7, Firefox, Safari 1.2, Konqueror 3 und Opera 7 gibt es auch in alternativen Browsern die AJAX-Unterstützung.

XMLHttpRequest kann lediglich eine HTTP-Anfrage an einen Webserver schicken und die Rückgabe auswerten. Sprich, AJAX an sich ist eigentlich eine sehr simple Sache. Die große Kunst besteht dann darin, die Server-Rückgabe irgendwie auf der Seite darzustellen. Dazu verwenden Sie natürlich JavaScript, in der Regel DOM, manchmal auch DHTML. Das wurde bereits behandelt, weswegen dieses Kapitel ausschließlich zeigt, wie Sie den Datenaustausch mit dem Server realisieren.


18.2.1 HTTP-Anfragen senden und auswerten  

Das Ganze funktioniert in drei Schritten. Zunächst (Schritt 1) müssen Sie das entsprechende Element erzeugen. Beim Internet Explorer geht das folgendermaßen:

var http = new ActiveXObject("Microsoft.XMLHTTP");

Die ActiveX-Funktionalität von XMLHttpRequest steckt in der XML-Bibliothek von Microsoft; bei neueren Versionen müssen Sie die Versionsnummer explizit angeben. Die gute Nachricht: Obige Variante funktioniert immer, denn auch neuere XML-Versionen liefern noch das alte Objekt mit.

Andere Browser verwenden ein natives Objekt::

var http = new XMLHttpRequest();

Da das XMLHttpRequest-Objekt so populär geworden ist, hat sich Microsoft entschieden, im Internet Explorer 7 XMLHttpRequest ebenfalls als natives Objekt mitzuliefern. Ein Hintergrund ist auch, dass man ActiveX im Internet Explorer abschalten, aber trotzdem JavaScript zulassen kann. Im Internet Explorer 7 funktioniert auch noch der ActiveX-Ansatz, aber eben auch der Weg per nativem JavaScript-Objekt.

Eine entsprechende Abfrage, welcher Browsertyp vorliegt, könnte also wie folgt aussehen:

var http;
if (window.XMLHttpRequest) {
   http = new XMLHttpRequest();
} else if (window.ActiveXObject) {
   http = new ActiveXObject("Microsoft.XMLHTTP");
}

Alternativ können Sie natürlich auch mit verschachtelten try-catch-Anweisungen arbeiten.

Schritt 2: Jetzt müssen Sie eine Verbindung zur Zielseite herstellen (die sich aus Sicherheitsgründen innerhalb derselben Domain befinden muss). Geben Sie die URL und die Sendemethode ("GET" oder "POST" beispielsweise) an, und rufen Sie die Methode open() auf. Als Parameter übergeben Sie (GET- oder POST-)Parameter, die Sie bei der Anfrage mitschicken möchten (oder null, wenn Sie nichts übergeben möchten). Der dritte Parameter gibt an, wie die Kommunikation ablaufen soll:

gpBilder
 
false steht für synchron: Die Skriptausführung wird angehalten, bis die Daten vom Server zurückkommen.
gpBilder
 
true steht für asynchron: Die Skriptausführung geht weiter, denn die HTTP-Anfrage wird im Hintergrund ausgeführt.

In der Regel verwenden Sie asynchrone Kommunikation, denn dann steht nicht die gesamte JavaScript-Applikation, bloß weil der Webserver die Anfrage nur langsam abarbeitet:

http.open("GET", "datei.html", true);

Bei asynchronen Anfragen müssen Sie natürlich Bescheid bekommen, sobald das Ergebnis vom Server da ist. XMLHttpRequest regelt das über eine Callback-Funktion: eine Funktion, die aufgerufen wird, wenn Resultate vom Webserver kommen.

In der Eigenschaft onreadystatechange geben Sie den Namen dieser Callback-Funktion an (als Verweis, also ohne Anführungszeichen); alternativ verwenden Sie eine anonyme Funktion:

http.onreadystatechange = ausgeben;

oder alternativ:

http.onreadystatechange = function() { ... };

Jetzt fehlt nur noch die Funktion zum Ausgeben der Rückgabe Schritt 3. Dazu ist zunächst festzuhalten, dass das Ereignis readystatechange immer dann ausgelöst wird, wenn sich der Zustand des HTTP-XML-Objekts ändert. Das passiert beispielsweise bei der Initialisierung, beim Verbindungsaufbau zur Datei und eben auch, wenn die externe Ressource komplett geladen worden ist. Die zugehörige Eigenschaft heißt readyState und kann die folgenden fünf Werte annehmen:

gpBilder
 
0 – nicht initialisiert
gpBilder
 
1 – lädt
gpBilder
 
2 – fertig geladen
gpBilder
 
3 – wartet
gpBilder
 
4 – fertig

Wenn alles geklappt hat, hat die Eigenschaft readyState also den Wert 4. Sie müssen die Variable der XMLHttpRequest-Instanz also global machen, um von überall aus Zugriff darauf zu erhalten.

Wenn alles passt, enthält die Eigenschaft responseText den Inhalt der entfernten Datei; Sie können dann diese Daten weiterverarbeiten. Hier sehen Sie ein vollständiges Listing, das eine simpel gestrickte HTML-Datei einliest und ausgibt:

<html>
<head>
<title>AJAX</title>
<script type="text/javascript"><!--
var http = null;
if (window.XMLHttpRequest) {
   http = new XMLHttpRequest();
} else if (window.ActiveXObject) {
   http = new ActiveXObject("Microsoft.XMLHTTP");
}
if (http != null) {
   http.open("GET", "datei.html", true);
   http.onreadystatechange = ausgeben;
   http.send(null);
}

function ausgeben() {
   if (http.readyState == 4) {
      document.getElementById("Ausgabe").innerHTML =
         http.responseText;
   }
}
//--></script>
</head>
<body>
HTML vom Server:
<div id="Ausgabe"></div>
</body>
</html>

Bilder

Abbildung 18.5     Die HTML-Daten kommen vom Server.

Hier der Vollständigkeit halber noch der Inhalt der Datei datei.html:

<p>AJAX erm&ouml;glicht <i>spannende</i> Effekte</p>


18.2.2 Parameter senden  

Wie bereits erwähnt wurde, können Sie bei der Methode send() des XMLHttpRequest-Objekts POST-Parameter mit angeben; GET-Parameter schreiben Sie direkt in die URL. Um diese auszuwerten, benötigen Sie allerdings eine serverseitige Technologie. Das folgende PHP-Skript gibt alle GET- und POST-Daten als HTML-Fragment aus:

<h1>GET</h1>
<?php
   echo nl2br(htmlspecialchars(print_r($_GET, true)));
?>
<h1>POST</h1>
<?php
   echo nl2br(htmlspecialchars(print_r($_POST, true)));
?>

Im folgenden Listing übergeben Sie ein paar Parameter an das Skript:

<html>
<head>
<title>AJAX</title>
<script type="text/javascript"><!--
var http = null;
if (window.XMLHttpRequest) {
   http = new XMLHttpRequest();
} else if (window.ActiveXObject) {
   http = new ActiveXObject("Microsoft.XMLHTTP");
}
if (http != null) {
   http.open("GET", "getpost.php?a=1&b=2&c=3", true);
   http.onreadystatechange = ausgeben;
   http.send(null);
}

function ausgeben() {
   if (http.readyState == 4) {
      document.getElementById("Ausgabe").innerHTML =
         http.responseText;
   }
}
//--></script>
</head>
<body>
HTML vom Server:
<div id="Ausgabe"></div>
</body>
</html>

Wie Sie im Browser sehen können, gibt das serverseitige Skript die übergebenen Daten aus.

Bei POST ist die Sache nicht ganz so einfach, denn ein Webbrowser schickt bei POST-Anfragen immer einen speziellen HTTP-Header mit, um das serverseitige Skript darauf vorzubereiten. Die zugehörige Methode heißt setRequestHeader(), der HTTP-Header ist Content-Type, und der erforderliche Wert ist "application/x-www-form-urlencoded". Damit ergibt sich folgendes Listing:

<html>
<head>
<title>AJAX</title>
<script type="text/javascript"><!--
var http = null;
if (window.XMLHttpRequest) {
   http = new XMLHttpRequest();
} else if (window.ActiveXObject) {
   http = new ActiveXObject("Microsoft.XMLHTTP");
}
if (http != null) {
   http.open("POST", "getpost.php", true);
   http.onreadystatechange = ausgeben;
   http.setRequestHeader(
      "Content-Type",
      "application/x-www-form-urlencoded");
   http.send("a=1&b=2&c=3");
}

function ausgeben() {
   if (http.readyState == 4) {
      document.getElementById("Ausgabe").innerHTML =
         http.responseText;
   }
}
//--></script>
</head>
<body>
HTML vom Server:
<div id="Ausgabe"></div>
</body>
</html>

Abbildung 18.6 zeigt das Ergebnis: Das Skript empfängt die POST-Daten und schickt sie wieder zurück.

Bilder

Abbildung 18.6     Mit einem Extra-HTTP-Header funktionieren auch POST-Anfragen.


18.2.3 Mit komplexen Daten arbeiten – JSON  

In Kapitel 13 haben Sie zwei platzsparende JavaScript-Notationen kennengelernt:

gpBilder
 
Arrays können mit eckigen Klammern angegeben werden:
["eins", "zwei", "drei"]
gpBilder
 
Objekte können mit geschweiften Klammern angegeben werden:
{"Eigenschaft": "Wert", "Methode": function() { ... }}

Und obwohl das tatsächlich Teil der JavaScript-Spezifikation ist, wurde das Ganze erst bekannt, seitdem es einen Namen gibt. Der lautet JSON – JavaScript Object Notation. Eine eigene Homepage gibt es dafür sogar auch, nämlich http://json.org/.

Das Schöne an JSON ist, dass damit Arrays und Objekte sehr simpel als Zeichenkette angegeben werden können; man spricht hier auch von Serialisierung.

Der eigentliche Clou bei JSON ist, dass diese Strings sehr einfach wieder in JavaScript-Arrays oder Objekte zurückverwandelt werden können: mit eval()! Hier ein Beispiel:

var json_array = '["eins", "zwei", "drei"]';
var json_objekt = '{"Eigenschaft": "Wert", "Methode": function() { alert(this.Eigenschaft); } }';
var a = eval("(" + json_array + ")");
var o = eval("(" + json_objekt + ")");
alert(a.length); // gibt "3" aus
o.Methode(); // gibt "Wert" aus

In Verbindung mit AJAX sehen Sie, wie unverzichtbar JSON in nur kürzester Zeit geworden ist. Denn wenn Sie von der Serverseite her mehr als nur bloßen Text zurückgeben möchten, ist JSON ein perfektes Format dafür.

Im folgenden Kapitel lernen Sie einen anderen, aber deutlich komplizierteren Weg kennen.

Ein kleines Beispiel soll zeigen, was damit möglich ist. Erinnern Sie sich noch an das Beispiel in Kapitel 16, in dem dynamisch eine HTML-Aufzählungsliste aus einem JavaScript-Objekt erstellt worden ist? Dieses Beispiel finden Sie im Folgenden wieder, nur kommt diesmal das JavaScript-Objekt vom Server – in Form eines JSON-Strings:

<html>
<head>
<title>AJAX</title>
<script type="text/javascript"><!--
var http = null;
if (window.XMLHttpRequest) {
   http = new XMLHttpRequest();
} else if (window.ActiveXObject) {
   http = new ActiveXObject("Microsoft.XMLHTTP");
}
if (http != null) {
   http.open("GET", "json.txt", true);
   http.onreadystatechange = ausgeben;
   http.send(null);
}

function ausgeben() {
   if (http.readyState == 4) {
      var daten = http.responseText;
      daten = eval("(" + daten + ")");

      var liste = document.getElementById("Liste");
      for (var i = 0; i < daten.length; i++) {
         var link = daten[i];
         var li = document.createElement("li");
         var a = document.createElement("a");
         a.setAttribute("href", link.url);
         var txt = document.createTextNode(link.text);
         a.appendChild(txt);
         li.appendChild(a);
         liste.appendChild(li);
      }
   }
}
//--></script>
</head>
<body>
<ul id="Liste"></ul>
</body>
</html>

Hier der Vollständigkeit halber noch der Inhalt der Datei json.txt:

[ {"text": "Mozilla", "url": "http://www.mozilla.com/"}, {"text": "Microsoft", "url": 
"http://www.microsoft.com/"}, {"text": "Opera", "url": "http://www.opera.com/"} ]

In Abbildung 18.7 können Sie das Ergebnis sehen: Die Listendaten kommen aus der JSON-Datei.

Bilder

Abbildung 18.7     Die Liste wurde mit JSON-Daten gefüllt.

Die JavaScript-Funktion eval() ist relativ gefährlich, denn sie führt Code aus. Sie müssen also der Quelle, von der die JSON-Daten kommen, unbedingt vertrauen. Somit sind beispielsweise Daten von Ihrem eigenen Webserver in Ordnung, Daten aus der URL dagegen scheiden aus.


18.2.4 Anfragen abbrechen  

Bei AJAX-Anfragen per XMLHttpRequest gibt es leider einen kleinen Nachteil: Das Error-Management ist schwierig bis hin zu nicht möglich. Sie erfahren nämlich nicht, wenn eine Anfrage kein Ergebnis liefert oder zu einem Fehler führt. Das heißt, unter Umständen warten Sie vergeblich auf eine Rückgabe.

Mit der Methode abort() des XMLHttpRequest-Objekts brechen Sie eine Anfrage ab, wenn sie zu lange dauert. Um das zu demonstrieren, erstellen wir ein PHP-Beispiel namens langsam.php – ein Skript, das unter Umständen langsam läuft (einen zufälligen Wert zwischen eine und zehn Sekunden):

<?php
$zufall = rand(1, 10);
sleep($zufall);
?>
<p>AJAX erm&ouml;glicht <i>spannende</i> Effekte</p>

Kommen wir nun zum Listing mit der Timeout-Überprüfung. Zunächst geben Sie die URL des »langsamen« PHP-Skripts an. Kleiner Kniff am Rande: An die URL wird eine Zufallszahl als GET-Parameter angehängt. Das verhindert Browser-Caching, so dass das PHP-Skript bei jedem Aufruf auch eine andere Laufzeit hat:

http.open("GET", "langsam.php?" + Math.random(), true);

Dann erstellen Sie einen Timeout: Nach fünfeinhalb Sekunden soll die Anfrage abgebrochen werden:

id = window.setTimeout("abbrechen()", 5500);

In dieser Funktion abbrechen() rufen Sie die Methode abort() auf und geben eine entsprechende Meldung aus:

function abbrechen() {
   http.abort();
   document.getElementById("Ausgabe").innerHTML =
      "Die Anfrage dauerte zu lange.";
}

Kommt allerdings vor Ablauf der fünfeinhalb Sekunden ein Ergebnis vom Server, muss natürlich der Timeout gelöscht werden:

function ausgeben() {
   if (http.readyState == 4) {
      document.getElementById("Ausgabe").innerHTML =
         http.responseText;
      window.clearTimeout(id);
   }
}

Hier noch einmal der komplette Code im Überblick:

<html>
<head>
<title>AJAX</title>
<script type="text/javascript"><!--
var http = null;
var id = null;
if (window.XMLHttpRequest) {
   http = new XMLHttpRequest();
} else if (window.ActiveXObject) {
   http = new ActiveXObject("Microsoft.XMLHTTP");
}
if (http != null) {
   http.open("GET", "langsam.php?" + Math.random(), true);
   http.onreadystatechange = ausgeben;
   http.send(null);
   id = window.setTimeout("abbrechen()", 5500);
}

function ausgeben() {
   if (http.readyState == 4) {
      document.getElementById("Ausgabe").innerHTML =
         http.responseText;
      window.clearTimeout(id);
   }
}

function abbrechen() {
   http.abort();
   document.getElementById("Ausgabe").innerHTML =
      "Die Anfrage dauerte zu lange.";
}
//--></script>
</head>
<body>
HTML vom Server:
<div id="Ausgabe"></div>
</body>
</html>

Probieren Sie es aus: In etwa der Hälfte der Fälle erscheint ein Ergebnis vom Server, in der anderen Hälfte sehen Sie eine Fehlermeldung.

Bilder

Abbildung 18.8     Der Server war schnell genug.

Denken Sie immer daran: Wenn Sie PHP-Skripte ausführen, benötigen Sie einen PHP-fähigen Webserver und müssen die Beispiele per http://<servername>/<dateiname> aufrufen, nicht direkt über das Dateisystem.

Bilder

Abbildung 18.9     Der Server war zu langsam.

Mit der gezeigten Technik können Sie also einfach einen Mechanismus implementieren, der nach dem Ablauf von ein paar Sekunden erkennen kann, dass der Server Probleme hat, und dementsprechend darauf reagiert.


18.2.5 Weitere Möglichkeiten 
topBilder

Zum Abschluss des Technik-Blocks folgt hier noch ein kurzer Überblick, was das XMLHttpRequest-Objekt sonst noch bietet (die komplette Auflistung der Eigenschaften und Methoden finden Sie in der Referenz):

gpBilder
 
Das Auslesen aller (getAllResponseHeaders()) oder eines (getResponseHeader()) HTTP-Headers
gpBilder
 
Auswertung des HTTP-Statuscodes (status) samt textueller Beschreibung (statusText)
gpBilder
 
Zugriff auf den Rückgabewert als XML-DOM-Objekt (responseXML) – mehr dazu im nächsten Kapitel!

Auch die Verwendung von HTTP-Authentifizierung ist möglich, wenn der Benutzername und das Passwort in der URL angegeben werden:

http://Benutzer:Passwort@Servername/Pfad/Datei.xyz

Allerdings ist das eine fragwürdige Implementierung: Der JavaScript-Code und auch der HTTP-Verkehr sind nicht verschlüsselt, somit sind Benutzername und Passwort im Klartext einsehbar.

 

 

18.3 AJAX-Probleme (und -Lösungen) 
topBilder

So weit die notwendigen AJAX-Grundlagen, zumindest in Hinblick auf das XMLHttpRequest-Objekt. Alles Weitere rund um AJAX hat mit dem Objekt nur noch wenig zu tun, sondern setzt auf andere Techniken: CSS, DOM, DHTML.

Allerdings sollen abschließend noch einige Nachteile von AJAX geschildert und Lösungsmöglichkeiten skizziert werden. Der größte Nachteil ist ja offensichtlich: AJAX verlangt JavaScript, ohne JavaScript kein AJAX. Je nach Statistik ist der Wert ein anderer, aber etwa 90  % der Anwender können AJAX-Effekte verwenden, die restlichen 10  % haben Browser ohne oder mit deaktiviertem JavaScript. Allein aus diesem Grund sollten AJAX-Effekte immer nur ein schickes Beiwerk sein, aber nie die komplette Anwendungslogik tragen. Außer natürlich, Sie können auf die 10  % der Nutzer verzichten. Aber es gibt auch noch andere potenzielle Probleme, die es zu umschiffen gilt. Ausgangsbasis ist ein leicht modifiziertes Beispiel von vorhin: Erneut werden JSON-Daten vom Server geladen und als Liste ausgegeben, aber erst auf Knopfdruck (beziehungsweise Klick auf eine Schaltfläche):

<html>
<head>
<title>AJAX</title>
<script type="text/javascript"><!--
var http = null;
function ladeJSON() {
if (window.XMLHttpRequest) { http = new XMLHttpRequest(); } else if (window.ActiveXObject) { http = new ActiveXObject("Microsoft.XMLHTTP"); } if (http != null) { http.open("GET", "json.txt", true); http.onreadystatechange = ausgeben; http.send(null); }
}
function ausgeben() { if (http.readyState == 4) { var daten = http.responseText; daten = eval("(" + daten + ")"); var liste = document.getElementById("Liste"); for (var i = 0; i < daten.length; i++) { var link = daten[i]; var li = document.createElement("li"); var a = document.createElement("a"); a.setAttribute("href", link.url); var txt = document.createTextNode(link.text); a.appendChild(txt); li.appendChild(a); liste.appendChild(li); } } } //--></script> </head> <body> <ul id="Liste"></ul>
<form>
<input type="button" value="Laden" onclick="ladeJSON();" />
</form>
</body> </html>

Diese Anwendung hat ein paar mögliche Nachteile, um die wir uns im Folgenden kümmern werden. Und auch wenn das Beispiel sehr einfach ist – auch komplexere Praxis-AJAX-Anwendungen haben in der Regel die Nachteile, die im Folgenden besprochen werden. Unsere Gegenmittel wirken dann aber auch dort.


18.3.1 Bookmarks  

Nachteil Nummer 1: Das Beispiel erlaubt keine Bookmarks (sie heißen Favoriten im Internet Explorer und Lesezeichen in Mozilla-Browsern). Der ursprüngliche Zustand (leere Liste) der Seite hat dieselbe URL wie der Endzustand der Seite (gefüllte Liste, nach Klick auf die Schaltfläche).

Dazu gibt es zwei verschiedene Ansätze. Häufig wird hier der Begriff Permalink gebraucht: Das sind permanente Links, also solche, die sich nicht ändern. Nach jedem Zustandswechsel müssen Sie einen neuen Permalink erzeugen und somit Ihre Benutzer darauf hinweisen, diesen Link als Bookmark abzulegen.

So weit die allgemeinen Ausführungen für so gut wie alle AJAX-Anwendungen – jetzt kommen wir zur spezifischen Implementierung für das vorliegende Beispiel. Andere AJAX-Szenarien erfordern andere Implementierungen, aber das grundlegende Prinzip bleibt absolut dasselbe.

Die Beispielanwendung kennt nur zwei Zustände, weswegen die Implementierung recht übersichtlich ist. Per URL muss der Zustand mitgeteilt werden, beispielsweise im Query-String. So könnte der Query-String ?geladen bedeuten, dass der Zustand »Liste geladen« gewünscht wird; der Standardzustand ist »Liste nicht geladen«.

Wenn also die HTML-Seite geladen wird, wirft JavaScript einen Blick auf location.search und sucht nach dem String »geladen«. Wird JavaScript fündig, wechselt die Anwendung in den anderen Zustand und lädt die JSON-Daten vom Server:

window.onload = function() {
   if (location.search.indexOf("geladen") > –1) {
      ladeJSON();
   }
}

Nun fehlt nur noch der Permalink:

<a id="Link" href="bookmark1.html">Permalink</a>

Nach dem Laden der Liste, also innerhalb der Funktion ausgeben(), muss dieser Link entsprechend angepasst werden:

document.getElementById("Link").setAttribute(
   "href", "bookmark1.html?geladen");

Hier der komplette Code:

<html>
<head>
<title>AJAX</title>
<script type="text/javascript"><!--
var http = null;

window.onload = function() {
if (location.search.indexOf("geladen") > –1) { ladeJSON(); }
}
function ladeJSON() { if (window.XMLHttpRequest) { http = new XMLHttpRequest(); } else if (window.ActiveXObject) { http = new ActiveXObject("Microsoft.XMLHTTP"); } if (http != null) { http.open("GET", "json.txt", true); http.onreadystatechange = ausgeben; http.send(null); } } function ausgeben() { if (http.readyState == 4) { var daten = http.responseText; daten = eval("(" + daten + ")"); var liste = document.getElementById("Liste"); for (var i = 0; i < daten.length; i++) { var link = daten[i]; var li = document.createElement("li"); var a = document.createElement("a"); a.setAttribute("href", link.url); var txt = document.createTextNode(link.text); a.appendChild(txt); li.appendChild(a); liste.appendChild(li); } document.getElementById("Link").setAttribute( "href", "bookmark1.html?geladen"); } } //--></script> </head> <body> <ul id="Liste"></ul>
<a id="Link" href="bookmark1.html">Permalink</a>
<form> <input type="button" value="Laden" onclick="ladeJSON();" /> </form> </body> </html>

Bilder

Abbildung 18.10     Nach dem Laden der Daten ist der Permalink angepasst.

Doch dieses Vorgehen erfordert immer noch ein manuelles Eingreifen des Nutzers, was vor allem für unerfahrene Surfer etwas ungewohnt ist. Deswegen wäre es schön, wenn sich die URL bei jedem Statuswechsel selbst aktualisieren würde. Eine Veränderung des Query-Strings allerdings würde die Seite erneut vom Server laden; dieses Verfahren scheidet damit aus. Der einzige Ausweg: Setzen Sie eine Textmarke (location.hash)! Das lädt die Seite nicht neu, aber die URL gibt dann genau den Status an. Hier der veränderte Code, ohne extra Permalink:

<html>
<head>
<title>AJAX</title>
<script type="text/javascript"><!--
var http = null;

window.onload = function() {
   if (location.hash.indexOf("geladen") > –1) {
      ladeJSON();
   }
}
function ladeJSON() {
   if (window.XMLHttpRequest) {
      http = new XMLHttpRequest();
   } else if (window.ActiveXObject) {
      http = new ActiveXObject("Microsoft.XMLHTTP");
   }
   if (http != null) {
      http.open("GET", "json.txt", true);
      http.onreadystatechange = ausgeben;
      http.send(null);
   }
}
function ausgeben() {
   if (http.readyState == 4) {
      var daten = http.responseText;
      daten = eval("(" + daten + ")");

      var liste = document.getElementById("Liste");
      for (var i = 0; i < daten.length; i++) {
         var link = daten[i];
         var li = document.createElement("li");
         var a = document.createElement("a");
         a.setAttribute("href", link.url);
         var txt = document.createTextNode(link.text);
         a.appendChild(txt);
         li.appendChild(a);
         liste.appendChild(li);
      }

      location.hash = "geladen";
   }
}
//--></script>
</head>
<body>
<ul id="Liste"></ul>
<form>
   <input type="button" value="Laden"
      onclick="ladeJSON();" />
</form>
</body>
</html>

Bilder

Abbildung 18.11     Die URL der Seite passt sich jetzt automatisch an.


18.3.2 Zurück-Schaltfläche  

Ganz perfekt ist die Anwendung aber trotz Bookmark-Unterstützung immer noch nicht. Achten Sie im Browser darauf, wie sich die Zurück- und Vorwärts-Schaltflächen verhalten. Im Internet Explorer passiert gar nichts, die Schaltflächen bleiben ausgegraut. Das Laden der Liste erzeugt also keinen Eintrag in der Browser-History (Verlauf im Internet Explorer, Chronik im Firefox).

In Mozilla-Browsern ist das Verhalten ein wenig anders: Die Veränderung von location.hash erzeugt einen History-Eintrag. Wenn Sie also die Datei bookmark2.html laden und dann auf die Schaltfläche klicken, ändert sich die URL auf bookmark2.html#geladen; die Zurück-Schaltfläche springt dann zur URL bookmark2.html zurück. Allerdings wird dadurch die Liste nicht gelöscht.

Zunächst zum Mozilla-Problem. Beim Laden der Seite muss jetzt eben auch der Zustand »Liste nicht geladen« abgefangen werden. Ein erster Ansatz sieht so aus:

window.onload = function() {
   if (location.hash.indexOf("geladen") > –1) {
      ladeJSON();
   } else {
      document.getElementById("Liste").innerHTML = "";
   }
}

Allerdings scheitert das am Caching-Mechanismus der Browser: Das load-Ereignis findet unter Umständen nicht statt, wenn per Browser-Schaltfläche vor- und zurückgesprungen wird. Deswegen müssen Sie per setInterval() oder setTimeout() periodisch location.hash überprüfen. Bei einer Veränderung müssen Sie aktiv werden. Hier ein Vorschlag, wie das implementiert werden kann:

Der aktuelle Zustand (sprich, location.hash) wird in einer globalen Variablen abgespeichert:

var zustand = location.hash;

Die Funktion laden() wird insofern modifiziert, als dass zustand entsprechend angepasst wird:

var laden = function() {
   if (location.hash.indexOf("geladen") > –1) {
      ladeJSON();
   } else {
      document.getElementById("Liste").innerHTML = "";
   }
   zustand = location.hash;
}

Die Funktion laden() muss nun mehrfach ausgeführt werden: zum einen direkt beim Laden der Seite, zum anderen aber auch danach regelmäßig, um etwaige Änderungen an location.hash zu erfassen. Ein Wert von 500 Millisekunden erscheint geeignet:

window.onload = laden;
window.setInterval(function() {
   if (zustand != location.hash) {
      laden();
   }
}, 500);

Ein wichtiger Punkt fehlt noch: Wenn die Funktion ausgeben() den Wert von location.hash neu setzt, muss auch die Variable zustand angepasst werden, denn ansonsten würde der JavaScript-Code die Liste zweimal laden.

Hier der vorläufige Zwischenstand des Beispiels:

<html>
<head>
<title>AJAX</title>
<script type="text/javascript"><!--
var http = null;
var zustand = location.hash;


var laden = function() {
if (location.hash.indexOf("geladen") > –1) { ladeJSON(); } else { document.getElementById("Liste").innerHTML = ""; } zustand = location.hash;
}
window.onload = laden;
window.setInterval(function() {
if (zustand != location.hash) { laden(); }
}, 500);
function ladeJSON() { if (window.XMLHttpRequest) { http = new XMLHttpRequest(); } else if (window.ActiveXObject) { http = new ActiveXObject("Microsoft.XMLHTTP"); } if (http != null) { http.open("GET", "json.txt", true); http.onreadystatechange = ausgeben; http.send(null); } } function ausgeben() { if (http.readyState == 4) { var daten = http.responseText; daten = eval("(" + daten + ")"); var liste = document.getElementById("Liste"); for (var i = 0; i < daten.length; i++) { var link = daten[i]; var li = document.createElement("li"); var a = document.createElement("a"); a.setAttribute("href", link.url); var txt = document.createTextNode(link.text); a.appendChild(txt); li.appendChild(a); liste.appendChild(li); } location.hash = "geladen"; zustand = location.hash; } } //--></script> </head> <body> <ul id="Liste"></ul> <form> <input type="button" value="Laden" onclick="ladeJSON();" /> </form> </body> </html>

In Mozilla-Browsern funktionieren jetzt die Navigationsschaltflächen wieder, auch der Opera kooperiert; aber im Internet Explorer tut sich immer noch nichts. Das hat einen Grund: Dort erzeugen nur tatsächliche HTTP-Anfragen einen neuen Eintrag in der Verlaufsliste. Also bedarf es eines speziellen Tricks: In einem unsichtbaren Frame oder Iframe (siehe Kapitel 10) wird jedes Mal eine neue Datei geladen, wenn sich der Zustand der Seite ändert. Hier zunächst der Iframe, der per CSS-Anweisung unsichtbar gemacht wird:

<iframe
   src="history.html"
   name="historyframe"
   style="display:none"></iframe>

Bei jedem Zustandswechsel muss die Datei history.html neu geladen werden (und den aktuellen Zustand im Query-String erhalten). Dies kann in der Funktion laden() realisiert werden. Stimmt der aktuelle Zustand mit dem Query-String des versteckten Iframes überein? Falls nicht, muss sich das ändern:

with (window.frames["historyframe"].window.location) {
   if (search != "?" + escape(zustand.substring(1)) &&
       window.ActiveXObject) {
      search = "?" + escape(zustand.substring(1));
   }
}

Im Frame selbst wird ein Blick zurück geworfen: Ist der Zustand aus der Hauptseite (top.zustand) identisch mit dem Zustand, der an den Iframe übergeben wurde? Denn wenn der Ifame neu geladen wird, muss gegebenenfalls die Hauptseite aktualisiert werden. Deswegen hier der Code der Datei history.html:

<script type="text/javascript"><!--
if (window.ActiveXObject && // ist es ein IE?
   "?" + top.zustand.substring(1) !=
      unescape(location.search)) {
   top.location.hash =
      unescape(location.search.substring(1));
   top.laden();
}
//--></script>

Hier noch einmal zur Ergänzung das komplette Markup samt JavaScript-Code für die HTML-Seite:

<html>
<head>
<title>AJAX</title>
<script type="text/javascript"><!--
var http = null;
var zustand = location.hash;

var laden = function() {
   if (location.hash.indexOf("geladen") > –1) {
      ladeJSON();
   } else {
      document.getElementById("Liste").innerHTML = "";
   }
   zustand = location.hash;
}
window.onload = laden;
window.setInterval(function() {
   if (zustand != location.hash) {
      laden();
   }
   with (window.frames["historyframe"].window.location) {
      if (search != "?" + escape(zustand.substring(1)) &&
          window.ActiveXObject) {
         search = "?" + escape(zustand.substring(1));
      }
   }
}, 500);

function ladeJSON() {
   if (window.XMLHttpRequest) {
      http = new XMLHttpRequest();
   } else if (window.ActiveXObject) {
      http = new ActiveXObject("Microsoft.XMLHTTP");
   }
   if (http != null) {
      http.open("GET", "json.txt", true);
      http.onreadystatechange = ausgeben;
      http.send(null);
   }
}

function ausgeben() {
   if (http.readyState == 4) {
      var daten = http.responseText;
      daten = eval("(" + daten + ")");
      var liste = document.getElementById("Liste");
      for (var i = 0; i < daten.length; i++) {
         var link = daten[i];
         var li = document.createElement("li");
         var a = document.createElement("a");
         a.setAttribute("href", link.url);
         var txt = document.createTextNode(link.text);
         a.appendChild(txt);
         li.appendChild(a);
         liste.appendChild(li);
      }

      location.hash = "geladen";
      zustand = location.hash;
   }
}
//--></script>
</head>
<body>
<ul id="Liste"></ul>
<form>
   <input type="button" value="Laden"
      onclick="ladeJSON();" />
</form>
<iframe
src="history.html" name="historyframe" style="display:none"></iframe> </body> </html>

Das Ergebnis der ganzen Mühe: Das Beispiel funktioniert immer noch in Mozilla-Browsern; der Internet Explorer spielt jetzt aber auch mit. Jedes Neuladen des Iframe erzeugt einen Eintrag in der Verlaufsliste des Browsers, wie Sie in Abbildung 18.12 sehen können. Durch unseren zusätzlichen Code führt ein Sprung zurück oder nach vorne dazu, dass der Seiteninhalt aktualisiert wird.

Sie sehen: Die größten AJAX-Nachteile, die Bookmarkfähigkeit und das Vor-/Zurückspringen, lassen sich mit etwas Code lösen. So weit die gute Nachricht. Die schlechte Nachricht ist: Die Programmierung kann recht aufwändig werden, gerade beim letzteren Punkt. Die genauen Details der Implementierung hängen natürlich auch sehr stark von dem Anwendungsszenario ab. Bei nur zwei möglichen Zuständen wie im Beispiel ist das natürlich einfacher, als wenn Sie auf einer Seite eine Handvoll dynamische DOM-Elemente verwenden.

Bilder

Abbildung 18.12     Mit einem Trick funktionieren auch beim Internet Explorer »Vor« und »Zurück« wieder.

Das Tutorial ist ein Auszug aus dem Buch von Stefan Münz:

Professionelle Websites - Programmierung, Design und Administration von Webseiten
Addison-Wesley, 2. Auflage, 1136 Seiten, gebunden, komplett in Farbe, mit DVD

Die Veröffentlichung des Kapitels erfolgt mit freundlicher Genehmigung von
Pearson Education.

Mehr Informationen zu diesem wunderbaren Fachbuch für Webmaster und Webentwickler
gibt es hier: Professionelle Websites

Alle Teile des Buches: 

1 Intro
2 HTML und CSS
3 Dynamische Seiten mit JavaScript/DOM
4 Die Server-Seite
5 PHP und MySQL
6 XML
7 Betrieb von Websites
8 Referenz
Bilder

DVD-Werbung
Kommentare
Achtung: Du kannst den Inhalt erst nach dem Login kommentieren.
Portrait von tanzfrosch
  • 11.01.2018 - 14:56

Dankeschön, tolles Tut. !

Portrait von MaoMao
  • 09.01.2013 - 17:08

Gute Tutorial leicht erklärt.

x
×
×