AJAX ohne AJAX
vom 4. September 2008Vorgestern bin ich auf ein Problem gestoßen, an das ich noch nicht gedacht hatte: Es ist nicht möglich, eine Datei mit einem XMLHTTPRequest zu versenden. Grund dafür ist JavaScript, welches in der Browser-Sandbox läuft und nicht auf lokale Dateien zugreifen kann.
Doch wie hat das GMail gelöst? Hier werden Dateianhänge nach dem Auswählen ohne ein Neuladen der Seite hochgeladen. Nach ein paar Experimenten und etwas Hilfe von ajaxpatterns.com fand ich ein paar interessante Browsereigenschaften, mit denen man GET- und POST-Requests auch ohne das XMLHTTPRequest-Interface absetzen kann.
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 Kontainer 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 Kontainer für das Abfangen der Antwort vom Server. Im folgenden wird 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'));
}
<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.
Kommentare
Man muss das input für die Datei daher immer schön von Hand ausfüllen, das Absenden darf dann gern via JS geschehen.
Cool - ich kannte das Problem, allerdings die genauen Details der Lösung nicht so sehr: JQuery bietet da nen Plugin an, dass es auch tut ... aber wie genau? Jetzt weiß ichs ;-)
Übrigens: Diese iFrame Geschichte war damals auch von Microsoft genutzt worden, ehe sie sich gedacht haben, dass so nen XMLHttpRequest doch cool wäre ... insofern bleibt zu hoffen, dass das noch was wird.
Gruß
Johannes