HTML5 File API, FormData und XMLHttpRequest2

Im letzten Beitrag zur HTML5 File API haben wir eine Bildvorschau erstellt, um dem Benutzer eine Vorschau der hochzuladenden Bilder zu präsentieren. Ich möchte hier das Thema der File API noch einmal aufgreifen, diesmal jedoch um einen Upload einer Datei mit dem neuen FormData Objekt sowie dem XMLHttpRequest Level 2 Objekt zu realisieren. Dabei kann der Fortschritt des Uploads direkt in der eigentlichen Seite angezeigt werden, ohne den Benutzer ohne Reaktion der Webseite warten zu lassen.

In den meisten eingesetzten Web-Formularen, welche eine Upload Funktion bieten ist der Nutzer gezwungen lange auf den eigentlichen Upload zu warten und wird somit in seiner Arbeit unterbrochen. Besonders bei großen Dateien kann die Wartezeit ärgerlich sein. Lange Zeit konnte man nicht in den Uploadprozess eingreifen und dem Benutzer keinen Fortschritt anzeigen. Das hat nun ein Ende denn mit HTML5 kommen neue Objekte, welche genau dies ermöglichen.

Beginnen wir wieder mit der eigentlichen HTML Seite, welche alle benötigten Elemente enthält:

<!DOCTYPE html>
<html>
    <head>
        <title>HTML5 - FileAPI - Upload mittels AJAX Request</title>
        <script type="text/javascript">
            // Hier fügen wir Code ein
        </script>
    </head>
    <body>
        <div class="wrapper">
            <form action="" method="post" enctype="multipart/form-data" id="upload-form">
                <label for="file-upload">Datei:</label>
                <input type="file" id="file-upload" name="file-upload" />
            </form>
            <label>Dateiname: <input type="text" id="file-name" /></label>
            <label>Fortschritt: <input type="text" id="progress" /></label>
        </div>
    </body>
</html>

Diese Seite beinhaltet wieder ein HTML Formular mit einem Feld zur Dateiauswahl, damit der Benutzer bestimmen kann welche Dateien hochgeladen werden sollen. Ebenso benötigen wir zwei Felder für die Anzeige des Dateinamens und des Fortschritts. Im <head>-Tag wurde bereits ein <script>-Tag eingefügt, welches wir nun nach und nach mit Code füllen werden.

Zu Beginn muss wieder sichergestellt sein, dass alle HTML Elemente bereits geladen sind bevor unser JavaScript ausgeführt wird. Dazu fügen wir dem „window“-Objekt einen neuen EventListener hinzu:

// Sicherstellen, dass das HTML geladen wurde
window.addEventListener('load', function() {
    var upload   = document.getElementById('file-upload');

    // EventListener, welcher überwacht ob eine neue Datei gewählt wurde
    upload.addEventListener('change', function(evt) {
    	alert('Neue Datei wurde gewählt.');
    }, false);
}, false);

Sobald das „load“-Event auftritt können wir sicher sein, dass jegliches HTML Element geladen wurde und per JavaScript darauf zugegriffen werden kann. In Zeile 3 holen wir uns das <input>-Element mit der ID „file-upload“, um dieses mit einem weiteren EventListener zu bestücken – dem „change“-Event. Aktuell gibt dieses Script eine Meldung aus, sobald eine neue Datei gewählt wurde, wie auf dieser Beispielseite:

https://www.checkdomain.de/blog/beispiele/html5/file-api/upload/schritt-1.html

XMLHttpRequest2 Objekt vorbereiten

Als nächstes können wir das XMLHttpRequest2 Objekt, welches für den Versand der gewählten Datei zuständig sein wird, vorbereiten:

// Sicherstellen, dass das HTML geladen wurde
window.addEventListener('load', function() {
    var upload   = document.getElementById('file-upload'),
        filename = document.getElementById('file-name'),
        progress = document.getElementById('progress');

    // EventListener, welcher überwacht ob eine neue Datei gewählt wurde
    upload.addEventListener('change', function(evt) {
        var request = new XMLHttpRequest();

        // XMLHttpRequest öffnen
        request.open('POST', 'upload.php', true);
    }, false);
}, false);

In den Zeilen 4 und 5 ermitteln wir vorerst zwei weitere Felder, um diese mit Informationen aus der File API zu bestücken. Ab Zeile 9 wird nun das XMLHttpRequest2 Objekt erstellt, mit dessen „open“-Methode in Zeile 12 ein „POST“-Request an die „upload.php“ vorbereitet wird.

https://www.checkdomain.de/blog/beispiele/html5/file-api/upload/schritt-2.html

FormData-Objekt erstellen und füllen

Als nächstes müssen wir das „FormData“-Objekt erstellen und mit den entsprechenden Daten füllen, welche an das Upload Script übermittelt werden sollen.

// Sicherstellen, dass das HTML geladen wurde
window.addEventListener('load', function() {
    var upload   = document.getElementById('file-upload'),
        filename = document.getElementById('file-name'),
        progress = document.getElementById('progress');

    // EventListener, welcher überwacht ob eine neue Datei gewählt wurde
    upload.addEventListener('change', function(evt) {
        var request = new XMLHttpRequest(),
            file = this.files[0];

        filename.value = file.name;

        // XMLHttpRequest öffnen
        request.open('POST', 'upload.php', true);

        // FormData Objekt erstellen
        var data = new FormData();
        data.append('file', file);
    }, false);
}, false);

Die eigentliche Datei welche hochgeladen werden soll, wird in Zeile 10 ermittelt. In unserem Beispiel handelt es sich immer nur um eine einzige Datei, weshalb wir auch nur das erste Element der Auflistung benötigen. Der Dateiname wird in Zeile 12 in das entsprechende Feld eingesetzt, damit der Benutzer noch einmal eine Bestätigung erhält, welche Datei hochgeladen wurde.
In den Zeilen 17-19 erstellen wir nun das gewünschte „FormData“-Objekt, welches alle zu übermittelnden Daten enthält. In diesem Beispiel ist dies nur die Datei, welche hochgeladen werden soll und mit der „append“-Methode hinzugefügt wird. Weitere Felder können dem „FormData“-Objekt ebenfalls mit der „append“-Methode übergeben werden.

Deinen bisherigen Code kannst du mit dieser Beispielseite vergleichen. Eine Meldung wird in diesem Abschnitt nicht ausgegeben:

https://www.checkdomain.de/blog/beispiele/html5/file-api/upload/schritt-3.html

Der eigentliche Upload

Bevor wir den eigentlichen Upload nun starten, wollen wir uns nun noch neue „EventHandler“ des XMLHttpRequest2 Objektes zu nutze machen.

// Sicherstellen, dass das HTML geladen wurde
window.addEventListener('load', function() {
    var upload   = document.getElementById('file-upload'),
        filename = document.getElementById('file-name'),
        progress = document.getElementById('progress');

    // EventListener, welcher überwacht ob eine neue Datei gewählt wurde
    upload.addEventListener('change', function(evt) {
        var request = new XMLHttpRequest(),
            file = this.files[0];

        filename.value = file.name;

        // XMLHttpRequest öffnen
        request.open('POST', 'upload.php', true);

        // EventListener für den Fortschritt und das Abschliessen des Uploads
        request.upload.addEventListener('progress', function(evt) {
            progress.value = Number((100 / evt.total) * evt.loaded).toFixed(2) + ' %';
        }, false);
        request.addEventListener('load', function(evt) {
            alert('Upload komplett');
        }, false);

        // FormData Objekt erstellen
        var data = new FormData();
        data.append('file', file);

        // Datei hochladen
        request.send(data);
    }, false);
}, false);

In den Zeilen 17-23 fügen wir nun die EventListener hinzu. Speziell möchte ich hier das „progress“-Event hervorheben. Dieses wird im Falle eines Uploads mindestens einmal gefeuert. Status Aktualisierungen finden also optimal in diesem Event ihren Platz. In unserem Beispiel ermitteln wir über die Eigenschaften „total“ und „loaded“ des Event-Objektes den aktuellen Fortschritt in Prozent in Zeile 19 und schreiben diesen in das input-Tag mit der ID „progress“.
Das „load“-Event kann dazu benutzt werden abschließende Aktionen auszuführen, wie z.B. das Anzeigen einer Meldung wie in unserem Fall oder falls es sich um mehrere Uploads handelt, könnte man den fertigen Upload auch als erledigt markieren.

In Zeile 31 wird zuletzt noch der Upload gestartet und das zuvor erstellte „FormData“-Objekt übergeben.

Fertig! Auf der Beispielseite funktioniert der Upload nun mit einem entsprechend neuen Browser reibungslos.

https://www.checkdomain.de/blog/beispiele/html5/file-api/upload/schritt-4.html

Hast du dieses Beispiel lokal ausprobiert wirst du zu diesem Zeitpunkt noch einen 404 HTTP Fehler erhalten, da die „upload.php“-Datei noch nicht Verfügbar ist sofern du diese noch nicht selbst angelegt hast.

Die PHP Seite

In der benötigten „upload.php“-Datei kann nun über die bekannten Funktionen und Superglobalen Variablen wie $_FILES auf die hochgeladene Datei mit folgendem Code zugegriffen werden:

<?php
if (is_array($_FILES['file'])) {
	move_uploaded_file($_FILES['file']['tmp_name'], 'pfad/zur/zieldatei');
}

Damit neigt sich auch diese Anleitung dem Ende. Sollten bei deinen Versuchen diese Anleitung nachzustellen Probleme auftreten, zögere nicht in den Kommentaren zu fragen.

VN:F [1.9.22_1171]
Rating: 4.4/5 (11 votes cast)
HTML5 File API, FormData und XMLHttpRequest2, 4.4 out of 5 based on 11 ratings

18 Gedanken zu „HTML5 File API, FormData und XMLHttpRequest2

  1. Hey Benjamin ,
    ich möchte in deinem JS zusätzlich eine eigene Variable in die PHP übergeben.
    Um der Grafik einen dynamischen Namen zu geben und diesen auch in meine Datenbank zu schreiben.

    JS: request.send(‚test=’+“bildname123″);
    PHP: $imgname= $_POST[‚test‘];
    funktioniert aber leider nich, auch nicht wenn ich deine „var data“ selbst mit Text fülle.
    Wie bekomme ich gleichzeitig mit dem Bild auch einen Wert aus der Form in die upload.php?

    1. Ich habe es inzwischen durch einen sehr großen Zufall hinbekommen.
      var testvar = „bildname123“;
      data.append(‚file‘, file, testvar);
      Die dritte Stelle in den Klammern kommt dann im php darüber an, für mich recht verwunderlich.^^
      $phpvar= $_FILES[‚file‘][’name‘];

      Jetzt bräuchte ich aber noch eine zweite Variable die ich in die php bekomme.
      Um den Bildnamen schon im js vor dem php zusammenzubauen, um gleich das hochgeladene Bild anzuzeigen und trotzdem noch einen Teil des Bildnamens in der php einzeln zu haben, um das Bild einem User in der DB zuzuordnen.

    2. Moin Jack,

      im Beispiel wird ja die URL „upload.php“ aufgerufen. Du kannst dort noch weitere Variablen anhängen und dann in deinem PHP Script mit der superglobalen Variable „$_GET“ auslesen. Das würde dann so aussehen: „upload.php?key=wert&key2=wert2“. In deinem PHP Script könntest du dann mit $_GET[‚key‘] auf den ersten Wert zugreifen.

    3. Moin Benjamin,
      vielen Dank! :)
      Ich hatte es inzwischen so gelöst alles in eine Variable zu packen und in der upload.php mit explode() wieder auseinander zu bauen.^^

      Die Kombination aus deinem Upload und deinem Bildvorschau Tutorial ist mir leider deutlich zu kompliziert, weshalb ich das Bild erst nach dem Upload anzeige, was voll okay für mich ist.
      Allerdings wäre es super wenn die Bilder beim bzw. vor dem hochladen automatisch kleiner skaliert werden.
      Am besten mit einer Max Größe so dass Große kleiner werden, aber Kleine nicht großgezogen und natürlich die Proportionen beibehalten werden.
      Wäre das möglich in dein Uploadscript zu integrieren?
      Ich hab schon mehrere Tage so einiges mit anderen Tutorials usw. versucht aber das ist echt zu hoch für mich.

    4. Ich kann dir dafür die Imagine Bibliothek empfehlen: https://github.com/avalanche123/Imagine

      Damit kannst du Bilder relativ einfach auf eine bestimmte Größe skalieren ohne die Proportionen zu verlieren.

    5. Okay scheint leider recht kompliziert und aufwendig zu sein für so eine kleine Aufgabe.
      Hab mir mehrere Librarys angesehen, bekomme aber nichts bei mir eingebaut.

      Scheint auch mit der FileReader API ohne php zu gehen.
      http://christopher5106.github.io/web/2015/12/13/HTML5-file-image-upload-and-resizing-javascript-with-progress-bar.html
      Aber ist mir leider auch 95% zu viel Code um es verstehen und handhaben zu können. Das Problem ist alles was du nicht in 2 Minuten kannst, kann ich auch in 2 Monaten nicht.
      Naja dann lass ich es erstmal ohne resize und nutze irgendwann vielleicht einen Uploader der es bereits integriert hat.
      Hier mal mein kleines Projekt was jetzt soweit fertig ist. http://bestfriend.gq
      Ist hauptsächlich für Smartphones gedacht, eine ordentliche Desktopansicht bekomme ich wohl nicht hin da ich überwiegend VW statt % verwendet habe und so nicht alles kleiner wird auch wenn ich das äußerste Div um die ganze Seite ja nach Useragent schmäler mache..
      Was wohl daran liegt dass es sich an Viewport orientierte, also nur wie gewünscht kleiner wird wenn ich das Browserfenster kleiner ziehe.

  2. Ho! :)

    Danke für dein Tutorial. Leider hab ich aber ein Problem beim empfangen der Daten, das Script sendet die Daten ab (request.send(data);), es ein Textblock mit den Daten gesendet aber es kommt nichts an, $_FILES und $_POST bleiben leer.

    Grüße
    Sio

    1. Ho! :)

      ich hab meinen Fehler gefunden. Das Problem war, das ich FormData() nicht richtig aufgerufen hatte. Dadurch wird der falsche Content-Type (multipart/form-data …) nicht gesetzt und es kann nichts ankommen.

      Grüße
      Sio

  3. funkioniert nicht bei mir, komplett-meldung kommt zwar, datei ist aber nach dem upload nciht vorhanden. mit „konventioneller“ methode funkt. es auf meinem system.

    was noch auffällt ist dass der error 404 http-fehler auf keinen fall kommt, auch wenn testweise ein php-script angegeben wird welches gar ncith vorhanden ist (also name der php-datei geändert wird).

    FF 20.0.1 (daran sollte es niht liegen)

  4. Ich habe es herausgefunden, ich war einfach ungenau, weil ich dachte ich müsste nur den Pfad zur Zieldatei angeben (ich mach das nicht jeden Tag) und nicht auch den Namen, der Datei.
    In eurem Script von upload.php stand ‚pfad/zur/zieldatei‘ jetzt habe ich ‚pfad/zur/zieldatei.jpg‘ und dann ging es.
    Danke für Euer Script!
    Ich werde nun noch versuchen die Vorschau nach dem Hochladen mit einzubauen.

  5. Ich habe leider das Problem, dass die Datei zwar scheinbar hochgeladen wird, dann aber nirgendwo auftaucht. Kann mir da jemand helfen?

    1. Mir geht es genau so. Ich habe alle Rechte geprüft, verschiedene Pfade probiert, aber irgendwie landet die Datei nicht auf dem Server.
      Ich habe auch zwei verschiedene Server versucht.

    2. Hallo Martin,

      gibt es denn irgendwelche Fehlermeldungen in der Javascript Konsole? Wird dein „upload.php“ Script überhaupt aufgerufen und abgearbeitet? Wenn ja, beinhaltet die Superglobale Variable $_FILES Informationen über deine hochgeladene Datei?

  6. Ich hab`s… ;-)

    request.onreadystatechange = callback;

    function callback()
    {
    // readyState 4: Request ist fertig
    if (request.readyState==4) {
    // Status HTTP-Header 200 – OK, 404 – Not found
    if (request.status == 200) {
    // responseText enthält die Ausgabe des PHP-Skripts
    var content = request.responseText;
    // Ausgabe in div-Container
    document.getElementById(„view“).innerHTML = content;
    }
    else {
    alert(„Fehler:“+request.status+“ – „+request.statusText);
    }
    }
    }

  7. Hi, ich überprüfe noch zusätzlich in der „upload.php“ den Fileupload.
    Wieso bekomme ich keine echo zurück?
    Kann mir evt. jemand weiterhelfen?

    Gruß Patrick

  8. Hey!
    Erst einmal vielen Dank für diesen tollen Beitrag.
    Bei mir klappts aber noch nicht ganz…

    Die Datei wird nicht wirklich hochgeladen. Über Firebug sehe ich, dass das Skript läuft.

    Meine Upload-Datei:

    Ich glaube, daran scheiterts… aber warum nur? :O

    Dankeschön schon einmal!!

    1. Hallo Dominik,

      ich habe dir per E-Mail geantwortet, damit du mir das Problem noch einmal genau erklären kannst und dein Codebeispiel mitsenden kannst. Hier wird Code natürlich aus deinem Kommentar entfernt.

      Besten Gruß
      Benjamin

  9. Super, genial. Vielen dank. Genial beschrieben.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.