[GELÖST] Datei(multi)upload mit FormData() ohne jQuery

psygonis

New member
Hallo,

ich habe ein kleines Problem.
ich habe eine kleine Anwendung, die ich mit Ajax steuere.
In meinem HTML Quelltext ist ein Formular, ähnlich wie dieses:

HTML:
<form action='execution/operatorData/edging_types/setEdgingTypes.php?token=".$siteTokenTemp."' name='formChangeEdgeNew' id='formChangeEdgeNewId' method='post' enctype='multipart/form-data'>
<article class='showLeft border_3 backgrund_4'>";
<Input type ........>
<Input type ........>
<Input type ........>
<Input type ........>
<Input type = 'submit' Name = 'senden' value = 'senden' onclick = 'setEdgingNew(\"formChangeEdgeNewId\", this.name, \"edging_new_file_id\");'>
</article>
</form>

Die Funktion setEdgingNew() fängt das Form ab und macht eventuelle Ausgaben, wenn der Request da ist.

HTML:
function setEdgingNew(formId, name){

  var tempObj3 = AJAXPost(formId, name, 0, 0);
                     
  if(tempObj3[0] == "false"){
    window.document.getElementById(tempObj3[2]).value = tempObj3[1] + "-> " + tempObj3[3];
    window.document.getElementById(tempObj3[2]).className = "errorText1";
    }else{
         window.document.getElementById(tempObj3[2]).value = tempObj3[1];
         window.document.getElementById(tempObj3[2]).className = "okText1";
         }

  return false;
  }

Es wird also das Form, bzw. die Daten an die Funktion AJAXPost() gesendet und dann der Request ausgewertet.

Die Funktion AJAXPost() macht den Hauptteil und da habe ich dann auch mein Problem.
Hier erst mal die Funktion AJAXPost():

HTML:
function AJAXPost(formId, name, table, openArray){
   var elem = document.getElementById(formId).elements;
   var params = "name=" + name + "&table=" + table;
    if(openArray != 0){params += "&openArray=" + encodeURIComponent(openArray); }
    params += "&";
    url = document.getElementById(formId).action;
    for(var i = 0; i < elem.length; i++){
      if (elem[i].tagName == "SELECT"){
        params += elem[i].name + "=" + encodeURIComponent(elem[i].options[elem[i].selectedIndex].value) + "&";
        }else{
             if(elem[i].type.toUpperCase() == "RADIO" || elem[i].type.toUpperCase() == "CHECKBOX"){
              if(elem[i].checked == true){
                params += elem[i].name + "=" + encodeURIComponent(elem[i].value) + "&";
                }
              }else{
                   if(elem[i].name != ""){
                     params += elem[i].name + "=" + encodeURIComponent(elem[i].value) + "&";
                     }
                   }
             }
       }



    xmlhttp5.open("POST", url, false);
    xmlhttp5.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xmlhttp5.setRequestHeader("Content-length", params.length);
    xmlhttp5.setRequestHeader("Connection", "close");
    xmlhttp5.send(params);

    alert(xmlhttp5.responseText);
    var ajaxObj = JSON.parse(xmlhttp5.responseText);

    return ajaxObj;

  }

Das funktioniert wunderbar. Und vor allem ohne große jQuery oder andere Plugins.

Aber jetzt habe ich ein Formular mit einem FILE Feld. Und da komme ich gerade an meine Grenzen.
Wie bekomme ich in meine Funktion den File-Upload mit rein ohne dass ich doch noch auf Frameworks zurückgreifen muss?
Ich hab da zwar ein paar Ansätze mit jQuery gefunden, aber genau das will ich ja eigentlich nicht.
Ich möchte einfach das FILE Feld in mein Script mit einbinden. Hab das auch was von FormData() gefunden. Daraus wird ich
aber auch nicht schlau.
HTML:
var files = document.getElementById('file-input-element').files;
var data = new FormData();
for(var i = 0; i < files.length; i++) data.append('file'+i, files[i]);
 xmlhttp5.send(data);
Ich hab das so gemacht (natürlich mit meinen ID`s):
HTML:
xmlhttp5.open("POST", url, false);
    xmlhttp5.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xmlhttp5.setRequestHeader("Content-length", params.length);
    xmlhttp5.setRequestHeader("Connection", "close");

   var files = document.getElementById('file-input-element').files;
   var data = new FormData();
   for(var i = 0; i < files.length; i++) data.append('file'+i, files[i]);
    xmlhttp5.send(params + data);
Da bekomme ich zwar ein FormData Objekt, aber damit kann ich leider nix anfangen.
Ich sehe grade den Wald vor lauter Bäumen nicht. Könnt Ihr mir helfen?
 
Zuletzt bearbeitet von einem Moderator:
AW: dateiupload

Ohne jQuery bin ich da etwas unsicher aber mit jQuery würde man das FormData() Object in den send() Parameter geben.
Mit jQuery sähe das ungefähr so aus, vielleicht hilft das zum Verständnis:
HTML:
<p><b>Ajax File Upload:</b></p>
<form enctype="multipart/form-data" id="fileuploadform">
	<input type="hidden" name="act" value="upload">
    <input type="file" name="upload">
    <input id="fileuploadbutton" type="button" value="Upload">
</form>
<progress></progress>
<div id="fileuploadergebnis"></div>
<script>
$('#fileuploadbutton').click(function(){
    var formData = new FormData($("#fileuploadform")[0]);
    $.ajax({
        url: 'test_jquery.pl',  //Server script to process data
        type: 'POST',
        xhr: function() {  // Custom XMLHttpRequest
            var myXhr = $.ajaxSettings.xhr();
            if(myXhr.upload){ // Check if upload property exists
                myXhr.upload.addEventListener('progress',progressHandlingFunction, false); // For handling the progress of the upload
            }
            return myXhr;
        },
        //Ajax events
//        beforeSend: beforeSendHandler,
        success: function(data) { $('#fileuploadergebnis').text(data); },
        error: function(jqXHR,textStatus,errorThrown) { $('#fileuploadergebnis').text('FEHLER '+textStatus+' '+errorThrown+'!'); },
        // Form data
        data: formData,
        //Options to tell jQuery not to process data or worry about content-type.
        cache: false,
        contentType: false,
        processData: false
    });
});
function progressHandlingFunction(e){
    if(e.lengthComputable){
        $('progress').attr({value:e.loaded,max:e.total});
    }
}
</script>
 
AW: dateiupload

danke, aber das hilft leider nur bedingt

ich brauch in dem FormDataObject eigentlich ja nur das File Feld.
alles andere wird ja richtig übertragen.
ich habe jetzt das FormDataObject an meine params angehangen und es wird auch übertragen, nur kann ich in dem Zielscript nicht auf das File Object zugreifen

ich habe eine existierende Verbindung, und xmlhttp5 wurde mittels xmlhttp5 = new XMLHttpRequest(); instanziert
meine FormDaten (außer dem FileUpload Feld) kommen alle an. ich möchte das auch gern so belassen, da ich dort noch verschiedenes ergänze.
ohne dem FileUpload kommen also alle Daten an und können in meinem Script verarbeitet werden

so sieht nochmal das Form aus:
HTML:
<form action='execution/operatorData/edging_types/setEdgingTypes.php?token=".$siteTokenTemp."' name='formChangeEdgeNew' id='formChangeEdgeNewId' method='post' enctype='multipart/form-data'>
<article class='showLeft border_3 backgrund_4'>";
<Input type ........>
<Input type ........>
<Input type ........>
<Input type ........>
<input type='file' class='edgingFile border_1' name='edging_new_file' id='edging_new_file_id'>
<Input type = 'submit' Name = 'senden' value = 'senden' onclick = 'setEdgingNew(\"formChangeEdgeNewId\", this.name, "edging_new_file_id", "edging_new_file");'>
</article>
</form>
Das Script "setEdgingTypes.php" ist vorhanden und wird auch aufgerufen.

setEdgingNew() ruft dann die eigentliche Funktion auf, die die Daten verarbeitet, das klappt alles einwandfrei

Mit folgendem Code hole ich die post Daten:
HTML:
if(isset($_REQUEST['token'])){$token = $_REQUEST['token'];}else{$token = false;}                          //eindeutiger Schlüssel
if(isset($_REQUEST['name'])){$name = $_REQUEST['name'];}else{$name = "no";}                                     //Name das Buttons, der geklickt wurde 
if(isset($_REQUEST['fileName'])){$fileName = $_REQUEST['fileName'];}else{$fileName = "no";}                   //Name des fileUpload Feldes

durch mein Javascript werden nun die Daten an das php Script zur Auswertung gesendet.
relevant ist dieser Teil (die var params ist da vorhanden, das & ist bereits am Ende angehangen)
HTML:
     if(fileID != ""){
      var files = document.getElementById(fileID).files;
      var data = new FormData();
      for(var i = 0; i < files.length; i++){                              //falls es mehrere file felder gibt werden diese hintereinander dem FormData Object hinzugefügt
        data.append('file'+i, files[i]);
        }
      }else{var data = "";}

    params += data;
    alert(params);

    xmlhttp5.open("POST", url, false);
    var contentLength = params.length + data.length;
    xmlhttp5.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xmlhttp5.setRequestHeader("Content-length", contentLength);
    xmlhttp5.setRequestHeader("Connection", "close");
    xmlhttp5.send(params);

    var ajaxObj = JSON.parse(xmlhttp5.responseText);

    return ajaxObj;

das alert(params) zeigt (alle meine Formelemente)&[object FormData]

in dem php Script sollten dann meine "normalen" params sein + das DataForm Object

über $_FILES[$fileName]['name'] sollte ich ja nun den Namen des geladenen Bildes ausgeben können
aber $_FILES[] ist leer,
warum ?
ich steh da voll auf dem Schlauch
 
AW: dateiupload

ich habe es geschafft, also für alle, die so etwas auch mal suchen

HTML:
if (window.XMLHttpRequest){         // code for IE7+, Firefox, Chrome, Opera, Safari
        xmlhttp5 = new XMLHttpRequest();
        }else{                          // code for IE6, IE5
               xmlhttp5= new ActiveXObject("Microsoft.XMLHTTP");
              }
HTML:
function AJAX_Multiform_Post(formId, name, table, openArray, fileID, fileName){
   //daten senden mit <input type=file ...> feld
    url = document.getElementById(formId).action;
   var temp;
   var data = new FormData();
   var fileData = window.document.getElementById(fileID).files

    for(var j = 0; j < fileData.length; j++){
      temp = 'fileField'+ j;
      data.append(temp, fileData[j]);
      }

   var elem = document.getElementById(formId).elements;
   data.append("name", name);
   data.append("table", table);
   data.append("fileName", fileName);

    if(openArray != 0){
      data.append("openArray", encodeURIComponent(openArray));
      }

    for(var i = 0; i < elem.length; i++){
      if (elem[i].tagName == "SELECT"){
        data.append(elem[i].name, encodeURIComponent(elem[i].options[elem[i].selectedIndex].value));
        }else{
             if(elem[i].type.toUpperCase() == "RADIO" || elem[i].type.toUpperCase() == "CHECKBOX"){
               if(elem[i].checked == true){
                 data.append( elem[i].name, encodeURIComponent(elem[i].value));
                 }
              }else{
                   if(elem[i].name != "" && elem[i].type.toUpperCase() != "FILE"){
                     data.append( elem[i].name, encodeURIComponent(elem[i].value));
                     }
                   }
             }
       }

    xmlhttp5.open("POST", url, false);

    xmlhttp5.setRequestHeader("Content-type", "multipart/form-data");
    //xmlhttp5.setRequestHeader("Content-length", params.length);
    xmlhttp5.setRequestHeader("Connection", "close");
    xmlhttp5.send(data);

    var ajaxObj = JSON.parse(xmlhttp5.responseText);

    return ajaxObj;

  }

Folgende Daten wurden von mir benötigt:

function AJAX_Multiform_Post(formId, name, table, openArray, fileID, fileName)

formId...........ID des Formulares
Name............Name des geklickten Buttoons //optional
table.............Tabelle für die weiterverarbveitung mit mysql //optional
openArray......weitere Werte zur Übergabe aus JavaScript, die nicht über das Formular kommen //optional
fileID.............ID des <input type='file' .... > Feldes
fileName........Name des <input type='file' .... > Feldes

in dem Script, welches über die var URL aufgerufen wird, kann man jetzt die Fomularelemente ganz normal mit $_REQUEST[] auslesen und weiterverarbeiten
und das alles ohne große Bibliotheken oder Frameworks

Eine Frage bleibt noch offen: Wie ermittle ich die Größe des FormData Objects?
 
AW: dateiupload

Danke dir für das Zeigen der Lösung. Aber am Ende ist es doch ziemlich viel Code geworden. Hast du keine Lust auf jQuery oder hat das andere Gründe?
 
AW: dateiupload

Ich weiß gern was da passiert. Mit jQuery mag es wohl leichter sein, aber ich hab keinen Einblick was da eigentlich passiert. Außerdem ist die jQuery Bibliothek schon noch ein wenig größer als mein Script. Es sind ja nur ca. 50 Zeilen.
Hier noch die verbesserte Lösung die auch mehrere file Felder verarbeiten kann (Beschreibung direkt im Code).

HTML:
if (window.XMLHttpRequest){         // code for IE7+, Firefox, Chrome, Opera, Safari
        xmlhttp5 = new XMLHttpRequest();
        }else{                          // code for IE6, IE5
              xmlhttp5= new ActiveXObject("Microsoft.XMLHTTP");
              }

function AJAX_Multiform_Post(formId, name, table, openArray, fileID, fileName){
   //daten senden mit <input type=file ...> feld
   var url = document.getElementById(formId).action;                //URL, zum php Script, wird aus dem action tag vom Form geholt
   var elem = document.getElementById(formId).elements;             //leist alle Form Felder aus

   var temp, newID;
   var fileData;
   var fieldNames = new Array();                 //speichert die Namen der File Felder, wenn mehrere davon auf der Seite sind
   var fieldNamesID = new Array();               //speichert die ID`s der File Felder, wenn mehrere davon auf der Seite sind
   var data = new FormData();                    //DataForm Object initialisieren (erst ab IE 10 verfügbar)

   var k = 0;
   var l = 0;

   newID = fileID + "[0]";
   while(window.document.getElementById(newID)){                       //alle Daten der File Felder einlesen
     fileData = window.document.getElementById(newID).files[0];
     if(fileData){
       temp = 'fileField'+ k;
       data.append(temp, fileData);
       fieldNames[l] = temp;
       fieldNamesID[l] = k;
       l++;
       }
     k++;
     newID = fileID + "[" + k + "]";
     }


   /**************************************************************************************
   im Script, welches das Form enthält, werden die file Felder als Array dargestellt
   <input type='file' class='edgingFile border_1' name='edging_new_file[1]' id='edging_new_file_id[1]'>
   <input type='file' class='edgingFile border_1' name='edging_new_file[3]' id='edging_new_file_id[3]'>
   <input type='file' class='edgingFile border_1' name='edging_new_file[2]' id='edging_new_file_id[2]'>
   <input type='file' class='edgingFile border_1' name='edging_new_file[4]' id='edging_new_file_id[4]'>

   weitere werte, die den Bildern zugeordnet sind, z.B. bild_active oder eine Kategorie, werden in
   hidden Feldern abgelegt, die auch die gleiche ID haben
    <input type='file' class='edgingFile border_1' name='edging_new_file[1]' id='edging_new_file_id[1]'>
    <input type='hidden' name='hide_edging_bild_active[1]' id='hide_edging_bild_active_id[1]'>

   im Auswertungsscript (php) erhält man jetzt die Array`s
   fieldNamesArray
   und
   fieldNamesIDArray

   in diesen Array werden die Daten per Komma getrennt an das Script (php) übergeben
   fieldNamesArray = fileField1,fileField3,fileField2,fileField4
   fieldNamesIDArray = 1,3,2,4

   über die Reihenfolge lässt sich die ID auslesen, die dem File Feld zugeordnet ist und so können die
   anderen Felder (hidden) direkt über die ID angesprochen werden
   $hide_edging_pic = explode(",", fieldNamesArray);
   $hide_edging_pic_id = explode(",", fieldNamesIDArray);
   ***************************************************************************************/

   //Formulardaten an das DataForm Object anhängen
   data.append("fieldNamesArray", fieldNames);           //enthält die namen der File Felder
   data.append("fieldNamesIDArray", fieldNamesID);       //enthält die id`s der File Felder
   data.append("fileCount", k);
   data.append("name", name);
   data.append("table", table);
   data.append("fileName", fileName);

    if(openArray != 0){
      data.append("openArray", openArray);
      }

    for(var i = 0; i < elem.length; i++){
      if (elem[i].tagName == "SELECT"){
        data.append(elem[i].name, elem[i].options[elem[i].selectedIndex].value);
        }else{
             if(elem[i].type.toUpperCase() == "RADIO" || elem[i].type.toUpperCase() == "CHECKBOX"){
               if(elem[i].checked == true){
                 data.append( elem[i].name, elem[i].value);
                 }
              }else{
                   if(elem[i].name != "" && elem[i].type.toUpperCase() != "FILE"){        //leere elemente auslassen, files auch - die sind ja schon drin
                     data.append( elem[i].name, elem[i].value);
                     }
                   }
             }
       }

    xmlhttp5.open("POST", url, false);

    xmlhttp5.setRequestHeader("Content-type", "multipart/form-data");                 //enctype festlegen, damit auch Bilddaten übertragen werden
    //xmlhttp5.setRequestHeader("Content-length", data.length);                       //data.length gibt es nicht, deswegen auskommentiert
                                                                                      //muss ich noch Möglichkeit finden, die Größe von DataForm Object auszulesen
    xmlhttp5.setRequestHeader("Connection", "close");
    xmlhttp5.send(data);                                                              //Daten versenden

    var ajaxObj = JSON.parse(xmlhttp5.responseText);                                  //response vom php Script mit JSON parsen und speichern
                                                                                      //durch den JSON parser werden die Arrays in kommagetrennte Werte gewandelt

    return ajaxObj;                                                                   //response der php Verarbeitung zurückgeben an das aufrufende Javascript

  }
 
Cool. Hab den Titel mal erweitert, dann findet man das später besser. Sowas wird dauernd gebraucht.
 
Wie ermittle ich die Größe des FormData Objects?
Das ist komplett unnötig, da du den "Content-Length"-Header eigentlich gar nicht per Hand befüllen musst. Der wird vom Browser automatisch bestimmt und befüllt, wenn du "POST" verwendest.

PS: Globale Variablen - v.A. XHR-Objekte - können sehr schnell Probleme bereiten. Solltest du also besser vermeiden.
PPS: das XHR-Zeugs für die alten IEs kannst du komplett rausschmeißen, da die FormData gar nicht kennen (und auch keine Alternative) und dein Skript dann sowieso nicht funktionieren kann.
PPPS: synchrone XHRs sind unelegant. Versuche deinen Aufbau doch auf asynchrone umzustellen. Bei konkreten Fragen dazu kannst du ja nachfragen.
 
Zurück
Oben