JQuery-Lösungen für typische Ajax-Probleme: Ajax mit jQuery meistern
jQuery bietet für die Verwendung von Ajax die Methode $.ajax an. Daneben existieren einige Methoden wie beispielsweise $.getJSON, $.get und andere, die vorkonfigurierte Varianten der Ajax-Methode darstellen sowie Ajax-Events wie „ajaxSuccess“ oder „ajaxComplete“ zur zentralen eventbasierten Ajax-Behandlung.
Mit Ajax kommen zur allgemein üblichen DOM-Script-Entwicklung einige weitere Herausforderungen hinzu:
- Zusätzliche Fehlerquellen: Mit Ajax kommen möglicherweise zusätzliche Fehler wie http-, Format- und Timeout-Fehler ins Spiel
- Ajax läuft grundsätzlich asynchron ab, was letztendlich dazu führt, dass Entwickler ihre Programme nicht linear coden können, sondern auf Events oder Callbacks warten müssen
- Ajax führt meist zu DOM-Updates, die in der Regel nicht vom allgemeinen Initialisierungscode erfasst werden und die Komplexität des Skripts steigern können
jQuery sammelt alle möglichen Ajax-Fehler und führt, sofern konfiguriert, den error-Callback aus beziehungsweise triggert das ajaxError-Event mit weiteren Informationen zum Fehler. In dem Bestreben, möglichst schnell zum Ziel zu kommen, fangen viele Entwickler mit der Behandlung der erfolgreichen Abarbeitung des Ajax-Requests an. Kommt es zu Fehlern, fällt die Ursachenforschung allerdings schwer.
Dabei muss das Skript zum Loggen von Ajax-Fehlern gar nicht zeitraubend sein. Die Ausgabe von Fehlermeldungen benötigt hier weder eine schöne Oberfläche noch wiederkehrende Arbeit. Entwickler können sie einmal schreiben und dann in jedem Projekt wiederverwenden.
$(document).ajaxError(function(e, xhr, opts, err){ var status = (xhr || {}).status; xhr = null; setTimeout(function(){ throw(e.type +' | '+ status +' | '+ opts.url); }, 0); });
Listing 1
Der Error-Eventhandler zeigt die wichtigsten Informationen bei Auftreten eines Fehler in der Fehlerkonsole an. Das verwendete „setTimeout“ führt dazu, dass die Ausführung des übrigen Codes nicht durch „throw“ unterbrochen wird. Die Referenz auf das Ajax-Objekt wird per Hand gelöscht, um Memory Leaks im Internet Explorer zu vermeiden. Der Eventhandler lässt sich in Formatierung und Umfang weiter an die eigenen Bedürfnisse anpassen (Auswertung von opts.data/err). Ist das Logging-Script für Ajax-Fehler fertig, kann man ersten Request absetzen.
Fehler im Eventhandler finden
In der Regel führen click- oder submit-Events zur Ausführung von Ajax-Requests. Kommt es hierbei zu Fehlern, wird die default-Aktion des Browsers ausgeführt und der Fehler erscheint nur für einen Bruchteil einer Sekunde in der Konsole.
Eine Möglichkeit, die default-Ausführung trotz Fehler zu verhindern, bietet der Aufruf der Methode „preventDefault“ (siehe Listing) bereits ganz am Anfang des Eventhandlers auf dem Event-Objekt. Dies ist zumindest im Live-Betrieb keine gute Idee. Tritt nämlich unerwartet ein Fehler auf, passiert aus Nutzersicht schlichtweg nichts. Führt man die Methode dagegen am Ende aus oder bricht die default-Aktion mit „return false“ ab, fällt die Webseite im Fehlerfall und bei sauberer unobtrusiver Programmierung [1] automatisch auf das Ajax-Fallback zurück. Die Webanwendung wird damit auf elegante Weise fehlertolerant.
$('a').click(function(e){ e.preventDefault(); });
Listing 2
Wesentlich geschickter ist es daher, den verwendeten JavaScript-Debugger so einzustellen, dass er bei Fehlern selbstständig stoppt beziehungsweise die Fehlerkonsole beim Wechsel der Seite nicht leert. Beides kann man in Firebug entsprechend einstellen. Im IE-Debugger ist dies bereits Default.
Animationen und Userfeedback
Mit der Latenz zwischen Ajax-Request und -Response geht einher, dass der User darüber informiert werden sollte, dass seine Interaktion gerade verarbeitet wird. Animationen gehören hierbei zu einer eleganten Möglichkeit, den „Busy“-Status der Webseite deutlich zu machen. Soll beispielsweise der alte Inhalt ausgefadet und dann der neue Content wieder eingefadet werden, steht man vor der Herausforderung, dass die genaue Dauer der Ajax-Verarbeitung nicht vorhergesagt werden kann. Erfolgt die Server-Antwort bereits nach sehr kurzer Zeit und wird dann der neue Content sofort eingefügt, kann der User den Inhaltswechsel bereits vor Ablauf des Fade-out-Effekts sehen, wodurch die Animation recht hölzern wirkt.
jQuery bietet zur Lösung dieses Problems die queue-Methode auf DOM-Ebene an. Mit der queue-Methode können Funktionen in eine Warteschlange gebracht werden, sodass sie erst nach vollständigem Ablauf der Animation aufgerufen werden. Mit „dequeue“ kann die Funktion wieder aus der Warteschlange entfernt werden, was wiederum die nächste in der Warteschlange befindliche Funktion aufruft.
//Event-Hanlder löst Ajax-Request aus myApp.requestUrl = function(e){ $('#main').fadeTo(400, 0) .parent().addClass('busy') ; $.ajax({ url: this.href, dataType: 'html', success: myApp.onSuccess }); return false; //oder e.preventDefault(); }; //success-Handler myApp.onSuccess = function(content, status){ $('#main').queue(function(){ $(this) .html(content).fadeTo(400,1).dequeue() .parent().removeClass('busy') ; }); };
Listing 3
Nachgeladenes DOM „re-enhancen“
Wird HTML nachträglich verändert, wirken die bei der Initialisierung hinzugefügten Eventhandler und angewendeten DOM-Manipulationen nicht auf das neue HTML. Vielmehr muss das neue HTML nochmals „jQueryfiziert“ werden. Eine Ausnahme sind Eventhandler, die mit der Methode „live“ hinzugefügt wurden. Muss der Initialisierungscode ausschließlich Eventhandler hinzufügen, kann man bei einer Ajax-Webseite demnach auf diese Methode zurückgreifen [2]. Muss der Initialisierungscode dagegen ebenfalls verschiedene DOM-Manipulationen übernehmen, empfiehlt sich ein eventgetriebener Ansatz mit custom-Events.
Hierbei wird der Code in mindestens zwei Teile geteilt. Der eine Teil übernimmt die normale Initialisierung und der andere wird praktisch mit jedem DOM-Update neu ausgeführt. Hierzu wird ein custom-Listener hinzugefügt, der sowohl initial als auch durch die Ajax-Success-Methode(n) getriggert wird. Durch diesen Aufbau können sich verschiedene JavaScript-Module bei Bedarf in DOM-Updates einklinken oder über solche informieren, ohne Abhängigkeiten zwischen den Modulen zu erzeugen.
myApp.init = function(){ //normaler init-code //Binden und erstes Ausführen des DOM-Change codes $(document).bind('HTMLchange', myApp.domChange); $.event.trigger('HTMLchange'); }; myApp.domChange = function(e){ //e.target referenziert immer das Element, welches getriggert wurde var context = e && e.target || document; $('div.tabs', context).tabs(); }; //starten von init bei DOMREADY $(myApp.init);
Listing 4
Sobald die onSuccess-Methode das neue HTML ins DOM einfügt, kann das custom-Event „HTMLchange“ am #main-Element getriggert werden. Da auch custom-Events den DOM-Baum nach oben bubbeln, erreichen sie das document-Objekt. Die target-Eigenschaft referenziert hierbei das #main-Element als Ursprungselement, so dass nur nach neuen div.tabs-Elementen gesucht wird, um hierauf nun die Aktionen auszuführen.
Ajax ist asynchron, so dass Requests nicht in der selben Reihenfolge abgearbeitet werden, wie sie abgeschickt wurden und auch nicht in der selben Reihenfolge wieder beim Client ankommen. Daher sollte man vorher entscheiden, ob es auf Reihenfolge und Synchronität ankommt. Ist dies der Fall, können diverse Ajaxmanager [3] und Ajaxqueues [4] helfen. Das Pluginrepository von jQuery [5] bietet hier eine große Auswahl.
Ajax und Barrierefreiheit
Eine weitere Hürde bei Ajax stellt die Zugänglichkeit dar. Assistive Technologien wie Screenreader müssen über HTML-Änderungen gesondert informiert werden, damit sie diese Inhalte vorlesen können. Kleinere HTML-Änderungen ohne Interaktionsinhalt, wie beispielsweise eine Änderung eines Warenkorbs, können durch die Aria-Liveregionen zugänglich gemacht werden [6]. Hierbei wird einfach das HTML-Element, dessen Inhalt sich ändert, mit dem Attribut „aria-live=“polite““ versehen. Hierdurch wird bei einer DOM-Änderung dieses Bereichs der neue Inhalt vorgelesen, ohne dass der Nutzer seinen aktuellen Ort/Fokus in der Seite ändert.
Bei größeren Bereichen und jenen, in denen Interaktionselemente wie Links oder Formulare vorhanden sind, sollte mit der DOM-Methode „focus“ die aktuelle Position im Dokument verschoben werden [7]. Die focus-Methode sollte hierbei innerhalb der setTimeout-Methode erfolgen. Nativ nicht fokusierbare Elemente können mit dem tabindex-Attribut fokusierbar gemacht werden.
//success-Handler myApp.onSuccess = function(content, status){ $('#main').queue(function(){ $(this) .html(content).fadeTo(400,1).dequeue().trigger(' HTMLchange') .parent().removeClass('busy') //Vorsicht: focus(1) ist ein a11y-Feature von jQuery UI, //welches setTimeout verwendet .find(':header:first').attr('tabindex', '-1').focus(1) ; }); };
Listing 5