Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

 <<   zurück
JavaScript und AJAX von Christian Wenz
Das umfassende Handbuch
Buch: JavaScript und AJAX

JavaScript und AJAX
839 S., mit DVD, 39,90 Euro
Galileo Computing
ISBN 3-89842-859-1
gp Kapitel 18 AJAX
  gp 18.1 AJAX-Beispiele
  gp 18.2 AJAX-Technik
    gp 18.2.1 HTTP-Anfragen senden und auswerten
    gp 18.2.2 Parameter senden
    gp 18.2.3 Mit komplexen Daten arbeiten – JSON
    gp 18.2.4 Anfragen abbrechen
    gp 18.2.5 Weitere Möglichkeiten
  gp 18.3 AJAX-Probleme (und -Lösungen)
    gp 18.3.1 Bookmarks
    gp 18.3.2 Zurück-Schaltfläche


Galileo Computing

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

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.


Galileo Computing

18.3.1 Bookmarks  downtop

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>

Abbildung
Hier klicken, um das Bild zu Vergrößern

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>

Abbildung
Hier klicken, um das Bild zu Vergrößern

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


Galileo Computing

18.3.2 Zurück-Schaltfläche  toptop

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.

Abbildung
Hier klicken, um das Bild zu Vergrößern

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

 




1  Werfen Sie mal einen Blick in dickere, spezifische AJAX-Bücher: Dort finden Sie häufig auch viele JavaScript-Grundlagen, denn mit AJAX allein lassen sich gar nicht so viele Seiten füllen.

 <<   zurück
  
  Zum Katalog
Zum Katalog: JavaScript und AJAX
JavaScript und AJAX
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: JavaScript und AJAX - Das Video-Training






 JavaScript und AJAX -
 Das Video-Training


Zum Katalog: Webseiten programmieren und gestalten






 Webseiten
 programmieren
 und gestalten


Zum Katalog: XHTML, HTML und CSS






 XHTML, HTML und CSS


Zum Katalog: CSS-Praxis






 CSS-Praxis


Zum Katalog: AJAX






 AJAX


Zum Katalog: PHP 5 und MySQL 5






 PHP 5 und MySQL 5


Zum Katalog: TYPO3 4.0






 TYPO3 4.0


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Galileo Press 2007
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de