Aaron Fischer Ingenieur, Vater, Heimwerker, Problemlöser

03 September, 2008

AJAX ohne AJAX

TL;DR:

Ist es möglich, XMLHTTPRequests zu versenden, auch wenn das Interface nicht zur Verfügung steht? Bei alten Browsern beispielsweise kann dies der Fall sein. Wie bekommt man dennoch AJAX-Requests (GET, POST, File Uploads) ohne ein Neuladen der Seite hin? Mit ein paar Tricks ist dies möglich.

Der iFrame Tag

Die Idee ist Folgende: Wenn per JavaScript ein GET-Request abgesetzt werden soll, wird dynamisch ein iFrame erzeugt und dessen src-Attribut auf eine URL gesetzt, die dann aufgerufen wird. Die GET-Parameter können dann wie gewohnt in Form von ?p1=val1...&pn=valn an die Adresse angehängt werden. Um die Server-Response abzufangen, bekommt das iFrame eine onLoad-Funktion verpasst, die den Inhalt des iFrames extrahiert, weiterverarbeitet und das iFrame wieder löscht. Das iFrame dient hier als eine Art Container zum Senden und Empfangen.

<script type="text/javascript">
function $(id) { return document.getElementById(id); }
function call() {
    var d = document.createElement("div");
    d.innerHTML =
        "<iframe id="iFrame" style="display: none;" " + 
        "onLoad="callback()"></iframe>";
    document.body.appendChild(d);

    $("iFrame").src = "server.php?val=" + $("var").value;
}
function callback() {
    $("result").innerHTML =
        $("iFrame").contentDocument.body.innerHTML; 
    $("iFrame").parentNode.removeChild($("iFrame"));
}
</script>

<p id="result"></p>
<form onSubmit="call(); return false;">
    <input type="text" id="var" />
    <input type="submit" />
</form>

Mit dieser Technik hat man einen fast vollwertigen Ersatz für das XMLHTTPRequest-Interface. Manche Frameworks nutzen dieses Feature, um auch ältere Browser zu unterstützen.

Doch was tun, wenn der Browser keine iFrames unterstützt? Hier kann man sich einer ähnlichen Technik bedienen: Sobald man einem img-Tag das src-Attribut verpasst, versucht der Browser das Bild zu laden. Hier lässt sich wie im iFrame-Beispiel auch eine URL angeben die zu einem Script führt und Parameter anhängen.

var img = document.createElement("img");
img.setAttribute("src", "server.php?p1=val1");

Dies ist natürlich ein one-way-ticket. Nach dem GET-Request kann der Server nur noch mit einem Bild antworten.

Und was ist mit POST?

Hier gibt es einen weiteren Trick, POST-Requests an den Server zu senden. Beim dynamischen Erstellen des iFrames fügt man diesem ein Formular hinzu, welches die Parameter als input-Tags beinhaltet. Mit JavaScript sendet man dann das Formular ab und extrahiert wiederum die Server-Response aus dem iFrame.

Einfacher geht es mit dem target-Attribut des form-Tags. Hier kann man ein iFrame als Target angeben. Das iFrame dient hier wiederum als Container für das Abfangen der Antwort vom Server. Im Folgenden wird die ausgewählte Datei auf den Server geladen:

<script type="text/javascript">
function $(id) { return document.getElementById(id); }
function call(form) {
    var d = document.createElement("div");
    d.innerHTML =
        "<iframe id="iFrame" name="iFrame" " + 
        "style="display: none;" " + 
        "onload="callback()"></iframe>";
    d.setAttribute("id", "repository");
    document.body.appendChild(d);
    return false;
}
function callback() {
    $("result").innerHTML =
        $("iFrame").contentDocument.body.innerHTML;
    $("repository").parentNode.removeChild($("repository"));
}
</script>

<p id="result"></p>

<form method="post" enctype="multipart/form-data" 
    action="server.php" target="iFrame"
    onSubmit="call(this);">

    <input type="file" name="file" />
    <input type="submit" />
</form>

Auf Serverseite wird die Datei nur noch entgegengenommen und in das richtige Verzeichnis geschoben.

foreach ($_FILES as $f)
   move_uploaded_file($f["tmp_name"], "upload/".$f["name"]);

Warum der Umstand?

Eigentlich erstaunlich, dass sich Web-Entwickler immer wieder solche dreckige Hacks einfallen lassen müssen, um ans Ziel zu kommen. Der Browser ist mittlerweile zum Plattformübergreifenden Betriebssystem mutiert, doch die Programmierung ist alles andere als komfortabel.

Mit dem neuen Google Browser Chrome kommt hoffentlich wieder etwas frischen Wind in den Browser-Markt. Ich warte derweil geduldig auf eine Linux-Version von Chrome.