Javascript XMLHttp-Request – Dateiupload

Datei Upload : Mehrere Videos, Musik, Textdateien, Bilder mit Javascript auf den Server laden

Für einen Datei-Upload vom lokalen Rechner auf den Server reichen schon ein einfaches HTML-Formular und wenige Zeilen PHP. Mit dem Attribut multiple im form-Element können mehrere Daten für einen Fileupload ausgewählt und hochgeladen werden.

23-02-02 SITEMAP CSS HTML JS Basis JS Web Tutorial SVG

Mehrere Dateien auf den Server laden

Der XMLHttp-Request (auch als AJAX bezeichnet) ist ein Multitalent: Er führt ein Update der Seite aus, holt Bilder und Daten vom Server und schickt Daten zum Server ohne die Seite neu zu laden. Mit Ajax 1 stand nur ein Event zur Verfügung: onreadystatechange. Ajax 2 beschert uns ein neues Event-Modell mit neuen Events, die eine detailliertere Abfrage der Vorgänge und des Zustands erlauben.

<form class="fileupload" action="upload.php" method="post">
    <input type="file" id="photos" name="photos[]" multiple>
    <button type="submit" id="upload-btn">Laden</button>
</form>

<div class="filelist"></div>
<div id="result"></div>

Thumbnail-Vorschau

Das einfache HTML-Formular würde zusammen mit der PHP-Anwendung schon ausreichen, um Dateien auf den Server zu laden. Allerdings läuft der Prozess dann kommentarlos ab und der Benutzer würde weder über Fehler noch über Erfolg der Aktion unterrichtet und keine Vorschaubilder sehen. Erst etwas Javascript informiert den Benutzer über den Ablauf seines Uploads.

Dass die Thumbnails für die geladenen Bilder (zumindest bei kleinen Bildern) hier so schnell erscheinen, liegt natürlich daran, dass sie noch nicht auf den Server geladen sind, sondern noch im Speicher des Browsers liegen. Wenn große Bilddateien mit mehreren Megabyte ausgewählt werden, braucht der Browser sichtbar Zeit, um die Thumbnails zu erstellen.

Der tatsächliche Upload auf den Server beginnt, wenn die Formulardaten mit dem "Laden"-Button abgeschickt wurden.

Die Result-Zeile macht es sich einfach und zeigt nur die bloße JSON-Response der Anwendung auf dem Server.

dot Datei-Upload mit Javascript

AJAX 2 hat eine bessere Kontrolle des Datei-Uploads mitgebracht, HTML spendiert das progress-Tag, das mit Javascript in Gang gesetzt wird.

Mit den modernen Browsern – selbst IE10 – braucht der Datei-Upload auch kein jQuery mehr. Wenn IE10 allerdings noch unterstützt werden muss, dann sollten Javascript-Variablen noch mit var anstelle von let deklariert werden.

Die drei Variablen erfassen das Formular mit seinen <form>- <input>- und <button>-Elementen:

const form = document.querySelector('.fileupload');
const fileSelect = document.getElementById('photos');
const uploadButton = document.getElementById('upload-btn');

// Platzhalter für die Anzeige der Vorschaubilder

Als nächstes braucht das Script einen Event Listener, der auf das onsubmit-Event des Formulars wartet.

form.onsubmit = function(event) {
  event.preventDefault();

  // Den Text des Buttons als visuelles Signal ändern
  uploadButton.innerHTML = 'Uploading ...';

  // Hierhin kommt der Rest des Scripts
}

uploadButton.innerHTML = 'Uploading ...' ändert den Button-Text als Hilfestellung und Information für den Benutzer.

Dateien für den Upload vorbereiten

Der Event Listener startet mit dem Aufruf event.preventDefault(), also dem Objekt, auf dem das Submit-Event abgefangen wurde. Das verhindert, dass der Browser das Formular absendet. Der Ablauf wird jetzt von AJAX gesteuert.

// Die ausgeählten Dateien aus dem input-Element laden
const files = fileSelect.files;

Holt die gewählten Dateien aus dem Input-Element und speichert sie in der Variablen files.

// Ein FormData Objekt erzeugen.
const formData = new FormData();

Legt ein neues FormData-Objekt an. Mit FormData werden die key/value-Paare für den AJAX-Request konstruiert.

AJAX-Request zusammenstellen

Die Zusammenstellung des Requests ist der nächste Schritt. Das passiert in einem Loop über alle Dateien, in dem die Dateien in das gerade erzeugte formData-Objekt eingefügt werden. An dieser Stelle kann auch bereits geprüft werden, ob alle Dateien vom richtigen Typ sind.

// Alle ausgewählten Dateien durchlaufen
for (let i = 0; i < files.length; i++) {
    const file = files[i];

    // Dateityp prüfen.
    if (!file.type.match('image.*')) {
        continue;
    }

    // Datei in den Request setzen
    formData.append('photos[]', file, file.name);
}

Die Typ-Eigenschaft der Datei (file.type) wird als String zurückgegeben. Darum reicht ein einfaches JavaScript match() auf den gewünschten Dateityp. Wenn der Dateityp nicht passt, wird die Datei übersprungen (continue).

Am Ende der for-Schleife fügt die append-Methode des formDate-Objekts die Datei zur Übertragung hinzu.

FormData.append()

FormData erspart das Zusammenstellen eines Post-Requests / JSON-Objekts aus den Eingabefeldern des Formulars und kann Files, Blobs und Strings übernehmen und versenden.

// Files
formData.append(name, file, filename);

// Blobs
formData.append(name, blob, filename);

// Strings
formData.append(name, value); 

XMLHttpRequest

Der XMLHttpRequest übernimmt die Kommunikation mit der Anwendung auf dem Server und hat drei Parameter:

  1. die HTTP-Methode (POST),
  2. die URL der Anwendung auf dem Server (upload.php),
  3. ein Boolscher Wert, der festlegt, dass der Request asynchron (im Hintergrund) erfolgt.
// XMLHttpRequest aufstellen
const xhr = new XMLHttpRequest();

// Verbindung zur Anwendung aufbauen
xhr.open('POST', 'upload.php', true);

// Platzhalter für den Ladefortschritt

Warten auf eine Antwort vom Server

Der Datei-Upload braucht einen Event Listener, der das onload-Event abfängt. Die status-Eigenschaft des xhr-Objekts zeigt an, ob der Request erfolgreich ausgeführt wurde.

// Event Handler für die Response vom Server
xhr.onload = function () {
    if (xhr.status === 200) {
    // Dateien wurden hochgeladen
	
        document.querySelector("#result").innerHTML = xhr.responseText;
        uploadButton.innerHTML = 'Upload';
        
    } else {
        document.querySelector("#result").innerHTML = "Fehler beim Upload " + xhr.responseText;
    }
};

Jetzt gibt es nur noch eine Anweisung: den Request absenden:

// Daten übertragen
xhr.send(formData);

Auch wenn der XMLHttpRequest einfacher geworden ist – der asynchrone Ablauf ist nicht gerade intuitiv. Mit dem Abgang von IE11 (Support-Ende war Nov 2020) können wir auf die Kombination von Javascript fetch mit then umsteigen, wenn IE11 nicht weiterhin unterstützt werden muss.

Thumbnails vor dem Datei-Upload zeigen

Die Anzeige der Vorschaubilder fehlt noch im Script. Die Thumbnails werden angezeigt, wenn der Benutzer Dateien ausgewählt hat – beim change-Event auf das Input-Element (input id="photos"), nicht innerhalb von form.addEventListener.

Das FileReader()-Objekt liest den Rohdaten-Puffer aus dem FileList-Objekt, das durch die Auswahl der Dateien über das Input-Element erzeugt wurde. FileReader.onload feuert, wenn der Lesevorgang beendet ist.

document.getElementById('photos').addEventListener('change', function (event) {
    const files = event.target.files; // FileList object
    for (let i = 0, f; f = files[i]; i++) {
        if (!f.type.match('image.*')) {
            continue;
        }
        const reader = new FileReader();
        reader.onload = (function(theFile) {
            return function(e) {
                const span = document.createElement('span');
                span.innerHTML = ['<img class="thumb" src="', e.target.result, '" title="', escape(theFile.name), '"/>'].join('');
                document.querySelector ('.filelist').insertBefore(span, null);
            };
        })(f);
        reader.readAsDataURL(f);
    }
    document.querySelector ('.filelist').innerHTML = '';
});

FileReader.readAsDataURL() startet das Einlesen und enthält am Ende die Rohdaten der Data-URL.

document.querySelector ('.filelist').insertBefore(span, null) fügt die Thumbnails am Ende der Liste ein.

progress – der Fortschrittsbalken

Javascript konnte zwar den Dateinamen, den Dateityp sowie Höhe und Breite des Bildes lesen, aber hatte keinen Zugriff auf die Größe der Datei auf dem lokalen Rechner des Benutzers.

Die Funktion für den Ladefortschritt:

xhr.upload.onprogress = function (e) {
    let percentUpload = Math.floor(100 * e.loaded / e.total);
    progress.value = percentUpload;
}

Jetzt gibt das xhr-Objekt zurück:

event.loaded
Die Menge der Daten, die aktuell übermittelt wurden
event.total
Die Menge der Daten, die insgesamt zu übertragen sind

Mit percentUpload wird das value-Attribut des progress-Elements gefüttert. Je nachdem wie groß der Upload ist, springt der Wert schnell von 0 auf 100 oder zeigt bei großen Datenmengen deutlich den Fortschritt des Uploads.

Das Script und auch die PHP-Anwendung, in der die Daten geprüft und behandelt werden, sind nur ein Basis-Gerüst für einen Dateiupload. Dafür ist das Gerüst überschaubar und zeigt die generellen Abläufe des Uploads.

Javascript als RAW anzeigen.

PHP Upload mehrerer Dateien

Nur rudimentär: Nur PNG ist zugelassen, Dateigröße beschränkt. PHP lädt Dateien zunächst in ein temporäres Verzeichnis, erst wenn das so funktioniert, werden die Dateien in das endgültige Verzeichnis auf dem Server bewegt und aus dem temporären Verzeichnis gelöscht.

<?php
//  JSON – Ausgabe
function outputJSON($msg, $status = 'error'){
    header('Content-Type: application/json');
    die(json_encode(array(
        'data' => $msg,
        'status' => $status
    )));
}

/** Über alle Dateien laufen 
    und den Pfad für den Dateispeicher setzen
**/
$target_dir = " … Pfad zum Verzeichnis auf dem Server … ";

if ( isset($_FILES['photos']['name']) ) {
	$total_files = count($_FILES['photos']['name']);

	for($key = 0; $key < $total_files; $key++) {

		// Check if file is selected
		if (isset($_FILES['photos']['name'][$key]) 
                      && $_FILES['photos']['size'][$key] > 0) {
			
			// Check filetype
			if($_FILES['photos']['type'][$key] != 'image/png'){
				outputJSON('Dateityp muss PNG sein.');
			}
			
			if(!getimagesize($_FILES['photos']['tmp_name'][$key])){
				outputJSON('Datei ist kein Bild.');
			}
			
			// Check filesize
			if($_FILES['photos']['size'][$key] > 500000){
				outputJSON('Dateigröße überschreitet Max 500KB.');
			}
			
			// Check ob der Dateiname bereits existiert
			if(file_exists ($target_dir . $_FILES['photos']['name'][$key])){
				outputJSON('Eine Datei mit diesem Namen existiert bereits.');
			}

			$original_filename = $_FILES['photos']['name'][$key];
			$target = $target_dir . basename($original_filename);
			$tmp  = $_FILES['photos']['tmp_name'][$key];
			move_uploaded_file($tmp, $target);
		}
	}
   outputJSON ( $total_files . ' Dateien in das Verzeichnis UPLOADS geladen ',  'OK');  
}

Fotos von mobilen Geräten hochladen

Auch die mobilen Geräte wie Tablet oder Smartphone bieten den Upload von Dateien aus dem Album des Geräts an.

Firefox loading images from mobile device
Fotos vom iPad mit Firefox laden

Fotos aus der Kamera werden mit

	<input type="file" accept="image/*" capture="camera">

geladen.