Callback-Funktion für die Ajax-Klasse

Nun gibts wieder ein kleines Update meiner Ajax Klasse. Das "Problem" an der alten Version war, dass die Ausgabe einfach in ein HTML-Element geschrieben wurde. Dies ist natürlich eine Einschränkung, auch wenn es bei meinen Scripten zu 95% korrekt ist. Bei den anderen 5% muss die Ausgabe der Daten aber anders aussehen. Bisher war die folgende Zeile für die Ausgabe verantwortlich:
document.getElementById(obj.dst).innerHTML=http.responseText;

Die Lösung ist nun die Folgende: Man kann der Klasse wenn nötig eine Funktion mit dem Namen "response" hinzufügen und wenn die existiert wird sie mit den Daten als Parameter aufgerufen, sonst werden die Daten wie bisher einfach ins HTML-Element geschrieben. Aus der Zeile oben wird nun der folgende Codeschnippsel:
// wenn obj.response eine funktion ist, diese aufrufen
if(typeof(obj.response)=="function")
{
    obj.response(http.responseText);
}
else
{
    // antwort ins html element schreiben
    document.getElementById(obj.dst).innerHTML=http.responseText;
}

Und die Benutzerdefinierte "Callback-Funktion" erstellt man so:
ajax=new XMLHttp;

// benutzerdefinierte funktion
ajax.response=function(response)
{
    // inhalt ins html element schreiben
    document.getElementById(ajax.dst).innerHTML=response;

    // element mit einem scriptaculous effekt anzeigen
    Effect.BlindDown(ajax.dst);
}

// ajax request starten
ajax.start("rpc.php?id="+id,"element");

Das wars auch schon. Recht simpel aber es Funktioniert tadellos und ist in meiner Meinung auch sehr einfach zu handhaben. Die komplette Klasse sieht nun so aus:
// Objekt XMLHttp
// Param url: Url der aufzurufenden Datei
// Param dst: ID des HTML-Elements
XMLHttp=function()
{
    // Den Browsertyp auslesen
    var browser=navigator.appName;
    var obj=this;

    if(browser=="Microsoft Internet Explorer")             // IE
    {
        var http=new ActiveXObject("Microsoft.XMLHTTP");
    }
    else                                                   // Mozilla, Safari, Opera, usw.
    {
        var http=new XMLHttpRequest();
    }
    
    obj.start=function(url,dst,load)
    {
        http.abort();
        obj.dst=dst;

        // HTTP-Request senden
        // Parameter url als Adresse
        http.open("GET",url,true);

        // Wenn Status geaendert wurde
        http.onreadystatechange=function()
        {
            // ueberpruefen, ob daten gesendet wurden
            // readystate 4 = complete
            if(http.readyState==4)
            {
                // ueberpruefen, ob status vom server richtig ist
                // status 200 = OK
                if(http.status==200)
                {
                    // wenn obj.response eine funktion ist, diese aufrufen
                    if(typeof(obj.response)=="function")
                    {
                        obj.response(http.responseText);
                    }
                    else
                    {
                        // antwort ins html element schreiben
                        document.getElementById(obj.dst).innerHTML=http.responseText;
                    }
                }
                else if(http.status!=0)
                {
                    // HTTP Fehlercode zurückgeben
                    document.getElementById(obj.dst).innerHTML="Fehler:\nHTTP-Status: "+http.status+"\nHTTP-Statustext: "+http.statusText;
                }
            }
        }
        
        http.send(null);
    }
}

Caching von AJAX-Requests verhindern

Bei meiner Ajax Klasse hatte ich ein kleines Problem, dass der Internet-Explorer gewisse Ajax-Requests zwischengespeichert wurden. Dies ist ja oft nicht was man will, den schliesslich soll immer das neuste Resultat zurückgeliefert werden.
Zum einen kann man bei der Serverseitigen Datei, die mit dem Request aufgerufen wird mit den Headers eine Cache-Control bewirken. Leider hat das bei mir nicht immer geholfen. Funktionieren würde es bei PHP so:
<?
header
("Cache-Control: no-cache, must-revalidate");
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
?>


Die Lösung die bei mir funktioniert hatte war die folgende: Wenn bei der Url die angefragt wird zusätzlich noch ein Parameter mit einer zufälligen Zahl angehängt wird meint der Browser, dass er die Adresse zum ersten mal aufruft und nimmt so keine Daten aus dem Cache. Das sieht dann so aus:
ajax=new XMLHttp;
var url="rpc.php?param=test&rand="+Math.random();
ajax.start(url,"elem");

Update der Ajax-Klasse

Nun habe ich doch noch einen "Fehler" in meiner Ajax-Klasse gefunden. In meiner Web-Applikation kann der Benutzer in mehreren Inputfeldern Daten Filtern, die dann in einem Dix-Container ausgegeben werden. Mit einem listener auf den Felder wird überprüft, ob etwas eingegeben wurde und sobald dies der Fall ist, wird mittels Ajax die PHP-Datei aufgerufen und die Daten in den Container geschrieben. Soweit so gut, das hat mit der alte Klasse auch recht gut geklappt. Allerdings ist es ja so, dass je mehr Daten der Benutzer eingab, desto weniger Daten musste die PHP-Datei zurückgeben. Mehr Filterdaten=Weniger Daten.
Das bedeutet aber auch, dass die Requests die anfangs gestartet werden länger dauern als die die später gestartet werden. Nun kann es also sein (wenn der Benutzer schnell ist), dass zuerst ein Request gestartet wird, bei welchem eine grosse Datenmenge zurück kommt. In der Zwischenzeit wird ein nächster Request, vom selben Benutzer in derselben Session ein neuer Request mit mehr Filter-Daten gestartet, bei welchem weniger Daten zurückkommen. Dieser wird natürlich von PHP schneller abgearbeitet und wird auch schneller zum Browser übertragen. So werden diese Daten schön im Div-Container angezeigt. Soweit so gut, das Problem ist nun allerdings, dass noch der andere Request mit der grösseren Datenmenge läuft und sobald der fertig ist, werden die Daten angezeigt. Also die alten, die der Benutzer gar nicht sehen will.
Das bedeutet nun also, dass sobald ein neuer Request gestartet wird, der alte, noch laufende, abgebrochen werden soll. Allerdings sollen aber trotzdem mehre Requests paralell laufen können.

Das bedeutet, dass pro Objekt ein Request aufs mal laufen soll, aber man soll mehrere Objekte erstellen können, die dann paralell laufen können.

In meiner Lösung erstellt man nun als erstes eine Referenz zu einem Objekt in welchem ein XMLHttp Objekt erstellt wird. Wenn nun ein Request gestartet werden soll, ruft man die Methode start() mit URL, und ID des Ziels auf. Sobald ein neuer Request mit demselben Objekt gestartet wird, wird der alte abgebrochen.
ajax=new XMLHttp;
ajax.start("ajax.php?param=test","feld");
Weitere Requests können dann so gestartet werden:
ajax.start("ajax.php?param=blub","feld");
Und auch mehrere paralelle Requests sind kein Problem:
ajax1=new XMLHttp;
ajax2=XMLHttp;

ajax1.start("ajax.php?param=ajax1","feld1");
ajax2.start("ajax.php?param=ajax2","feld2");
Und die ganze Klasse sieht dann so aus:
// Objekt XMLHttp
// Param url: Url der aufzurufenden Datei
// Param dst: ID des HTML-Elements
XMLHttp=function()
{
    // Den Browsertyp auslesen
    var browser=navigator.appName;
    var obj=this;

    if(browser=="Microsoft Internet Explorer")             // IE
    {
        var http=new ActiveXObject("Microsoft.XMLHTTP");
    }
    else                                                   // Mozilla, Safari, Opera, usw.
    {
        var http=new XMLHttpRequest();
    }

    obj.start=function(url,dst)
    {
        // bereits laufende requests abbrechen
        http.abort();

        obj.dst=dst;

        // HTTP-Request senden
        // Parameter url als Adresse
        http.open("GET",url,true);

        // Wenn Status geaendert wurde
        http.onreadystatechange=function()
        {
            // ueberpruefen, ob daten gesendet wurden
            // readystate 4 = complete
            if(http.readyState==4)
            {
                // ueberpruefen, ob status vom server richtig ist
                // status 200 = OK
                if(http.status==200)
                {
                    // Antwort vom Server in HTML-Element schreiben
                    // dst als ID
                    document.getElementById(obj.dst).innerHTML=http.responseText;
                }
                else if(http.status!=0)
                {
                    // HTTP Fehlercode zurückgeben
                    document.getElementById(obj.dst).innerHTML="Fehler:\nHTTP-Status: "+http.status+"\nHTTP-Statustext: "+http.statusText;
                }
            }
        }

        http.send(null);
    }
}

Simple AJAX-Klasse

Ich habe vor ca zwei Jahren bereits einen kleinen Beitrag über Ajax geschrieben und muss das nun für ein Projekt im Geschäft neu aufrollen. Mit etwas OOP habe ich eine Ajax-Klasse geschrieben, die die Daten vom Server abholt und in ein beliebiges HTML-Element einfügt.
Mit dieser Klasse sind auch mehrere paralelle Anfragen möglich und das ganze funktioniert im Firefox, IE und Opera. Andere Browser habe ich (noch) nicht getestet. Die Klasse erwartet den Parameter Url (die Adresse die aufgerufen werden soll) und Dst (die ID des HTML-Elements für die Ausgabe). Mit einigen Veränderungen kann die Klasse die Daten vom Server auch anderst ausgeben und natürlich kann man das noch beliebig erweitert (Lade-Bilder usw.).
// Objekt XMLHttp
// Param url: Url der aufzurufenden Datei
// Param dst: ID des HTML-Elements
XMLHttp=function(url,dst)
{
    // Den Browsertyp auslesen
    var browser=navigator.appName;

    if(browser=="Microsoft Internet Explorer")             // IE
    {
        var http=new ActiveXObject("Microsoft.XMLHTTP");
    }
    else                                                   // Mozilla, Safari, Opera, usw.
    {
        var http=new XMLHttpRequest();
    }

    // HTTP-Request senden
    // Parameter url als Adresse
    http.open("GET",url,true);

    // Wenn Status geaendert wurde
    http.onreadystatechange=function()
    {
        // ueberpruefen, ob daten gesendet wurden
        // readystate 4 = complete
        if(http.readyState==4)
        {
            // ueberpruefen, ob status vom server richtig ist
            // status 200 = OK
            if(http.status==200)
            {
                // Antwort vom Server in HTML-Element schreiben
                // dst als ID
                document.getElementById(dst).innerHTML=http.responseText;
            }
            else
            {
                // HTTP Fehlercode zurückgeben
                alert("Fehler:\nHTTP-Status: "+http.status+"\nHTTP-Statustext: "+http.statusText);
            }
        }
    }
    http.send(null);
}


Das ganze wird nun folgendermassen aufgerufen. Hier wird die Adresse ajax.php?param=test angefragt und die Anwort vom Server wird in das HTML-Element (div/span/p/usw..) geschrieben.
ajaxObj=new XMLHttp("ajax.php?param=test","feld");

Tourenseite verbessert

Ich habe heute bei diesem herrlichen Wetter einige Zeit geopfert und meine Tourenseite etwas verbessert. Einen Unterschied sieht man wohl nicht, aber man merkt einen. Die Seite ist einiges schneller. Bisher wurden bei jedem Aufruf der Seite alle Bilder geladen. Am Anfang war das kein Problem, aber inzwischen sind schon 43 Touren in der Datenbank und das läppert sich mit all den Bilden schon zusammen.
Jetzt werden die Bilder nur beim öffnen einer Tour geladen. AJAX sei dank. Getestet habe ich es bisher im Firefox 2 und IE 7. Sollte jemand Probleme feststellen, so wäre ich über einen Bugreport froh. Sollte jemand noch andere Vorschläge oder Anmerkungen zu der Tourseite habe so bin ich ganz Ohr Auge.

Autokompletierung abschalten

Die autovervollständigen Funktionen der heutigen Browser sind ja schon schöne Sachen. Nur den ersten Buchstaben eintippen und schon werden schon benutzte Texte angezeigt. Manchmal möchte man das allerdings nicht. Zum Beispiel bei Ajax-Suchen wo die Vorschläge von eine Script zur Verfügung gestellt werden. Wenn nun der Browser auch noch seine Vorschläge anzeigt, kommt das zu hässlichen Überschneidungen.
Mit JavaScript kann man diese Funktion relativ simpel für bestimmt Textfelder deaktvieren. Die Methode setAttribute() macht das möglich. Zuerst schreibt man eine kleine Funktion, die entweder direkt in den Head oder halt in eine externe Datei eingebunden wird. Eine externe Datei hat en Vorteil, dass sie weniger Traffic verursacht, da sie gecacht werden kann.
function disableAutocompletion()
{
    var autocomplete=document.getElementById('user');
    if(autocomplete==null) return;
    autocomplete.setAttribute('autocomplete','off');
}

Diese Funktion wird nun via onLoad() beim Laden der Seite aufgerufen.
<body onLoad="disableAutocompletion()">

Und schon werden vom Browser keine Vorschläge mehr gemacht.

Update, 22.03.07 09:49:
Warum einfach wenns auch kompliziert geht. Mit autocomplete="off" direkt im Input-Feld lässt sich das auch abstellen.

Update 2, 22.03.07 10:24:
autocomplete scheint allerdings kein Valides XHTML Attribut zu sein.

Suche & Seitenzahlen

Einige kleinen neuerungen auf dieser Seite.
In der Navigation auf der linken Seite ist nun eine Ajax Suche zu finden. Einfach den Suchbegriff eintippen und die Resultate werden angezeigt. Ich denke, ich werrde die Ausgabe der Suchergebnisse früher oder später noch mit einem Layer machen, da das Fieldset doch relativ schmal ist und sich einige Titel so über mehrere Zeilen erstrecken. Würde mich über Feedback freuen.
Als zweites habe ich auf allen Blogseiten, das heisst Übersicht, Topics, Places usw. unten an der Seite Links für neuere und ältere Beiträge hingemacht. Man kann nun also alle Seiten ansehen.

Alles in allem ist es nun einfach ältere Beiträge zu finden. Ich hoffe, das wird auch genutzt.

Ajaxing

Für eine kleine Webapplikation im Geschäft, die wir verwenden werden um die Flugtarife zu verwalten, arbeite ich mich nun in Ajax ein. Ich war erstaunt wie einfach das war. Eine übersicht an Ajax Tutorial gibt bei Dr. Web. Ich habe mir die kürzeste Version rausgepickt und das ganze auf meine Bedürfnisse angepasst.
Als erstes benötigt man ein Objekt, mit dem man die Daten sendet bzw. empfängt. Dies sieht so aus:
function createRequestObject()
{
    var ro;
    var browser=navigator.appName;
    if(browser=="Microsoft Internet Explorer")
    {
        ro=new ActiveXObject("Microsoft.XMLHTTP");
    }
    else
    {
        ro=new XMLHttpRequest();
    }
    return ro;
}

var http=createRequestObject();

Um nun die Daten zu senden benötigt man eine weitere Funktion. Hier sendReq genannt:
function sendReq(url)
{
    http.open('get',url);
    http.onreadystatechange = handleResponse;
    http.send(null);
}

Diese Funktion sendet einen GET Request an die per Parameter übergebene URL. Danach wird die Funktion handleResponse() aufgerufen, welche die Daten verarbeitet.
function handleResponse()
{
    if(http.readyState == 4)
    {
        var response=http.responseText;
        document.getElementById('showData').innerHTML=response;
    }
}

Diese überprüft zuerst, ob die Übermittlung vollständig ist und schreibt die Daten dann in den div Container mit der ID showData. Hier die verschieden Werte und deren Bedeutung die http.readystate haben kann.
	0 = uninitialized
	1 = loading
	2 = loaded
	3 = interactive
	4 = complete

Diese funktionen werden aber nicht direkt aus der HTML-Seite aufgerufen. Ich habe dafür eine kleine Wrapperfunktion gebaut, die das ganze einwenig übersichtlicher macht.
function flights()
{
    var airline=document.filter.airline.value;
    var destination=document.filter.destination.value;
    var date=document.filter.date.value;
    var class=document.filter.class.value;

    sendReq('rpc.php?action=flights&airline='+airline+'&destination='+destination+'&date='+date+'&class='+class);
}

Wird nun also die Funktion flights() von der HTML-Seite aufgerufen, so liest das Script die Werte der Input-Felder airline, destination, date und class aus. Diese Daten werden mit einem GET Request an das PHP-Script rpc.php weitergegeben. Nach der Verarbeitung der Daten gibt das PHP-Script die Daten aus und die JS-Funktion schreibt sie in den Div-Container. Eigentlich recht simpel und es verweinfacht die Arbeit mit der Applikation merklich.