Jak usunąć plik z FileList

111

Buduję aplikację internetową typu „przeciągnij i upuść” do przesłania za pomocą HTML5, upuszczam pliki do div i oczywiście pobieram obiekt dataTransfer, który daje mi FileList .

Teraz chcę usunąć niektóre pliki, ale nie wiem jak, ani czy to w ogóle możliwe.

Najlepiej po prostu usunąć je z FileList; Nie potrzebuję ich. Ale jeśli nie jest to możliwe, czy zamiast tego powinienem napisać kontrole w kodzie, który współdziała z FileList? Wydaje się to kłopotliwe.

Heilemann
źródło
Po prostu ciekawy: dlaczego chcesz to zrobić? Dlaczego mówisz „nie mam z nich pożytku” o (niektórych) plikach wybranych przez użytkownika?
Marcel Korpel
23
Chyba bardziej polega na tym, że użytkownik może usunąć pliki przed przesłaniem. Jeśli pierwotnie wybrałeś 20, a następnie zdecydujesz, że w rzeczywistości nie chcesz przesyłać 14, to nie możesz po prostu usunąć tego, musisz zacząć wszystko od nowa (co jest trochę uciążliwe). Myślę, że utworzenie listy FileList tylko do odczytu jest złym niedopatrzeniem, chyba że nie widzę pewnych skutków dla bezpieczeństwa.
Rafael,
Jest to problem z bezpieczeństwem związany z usuwaniem plików bezpośrednio z wejściowej listy plików, ale możesz sklonować tę FileList natychmiast po zamknięciu okna dialogowego przesyłania pliku, a następnie zmodyfikować ten klon i użyć go podczas wysyłania przez ajax
alex_1948511

Odpowiedzi:

147

Jeśli chcesz usunąć tylko kilka wybranych plików: nie możesz. Wersja robocza File API , do której utworzono łącze, zawiera notatkę:

HTMLInputElementInterfejs [HTML5] ma readonly FileList atrybut, [...]
[podkr]

Czytając trochę wersji roboczej HTML 5, natknąłem się na interfejsy API elementów wspólnychinput . Wygląda na to, że możesz usunąć całą listę plików, ustawiając valuewłaściwość inputobiektu na pusty ciąg, na przykład:

document.getElementById('multifile').value = "";

Przy okazji, artykuł Korzystanie z plików z aplikacji internetowych może być również interesujący.

Marcel Korpel
źródło
1
Zauważ, że atrybut tylko do odczytu nie oznacza, że ​​nie możesz zmienić obiektu, na który wskazuje. Możesz manipulować FileList (jeśli to było możliwe), oznacza to po prostu, że nie możesz przypisać do niego nowej FileList.
Robin Berjon
1
@RobinBerjon Chrome wydaje się ignorować atrybut „readonly ”, podczas gdy FireFox nie zezwala na operacje zapisu. Niestety, twoja sugestia, aby po prostu manipulować FileList, również nie działa w FireFox.
borisdiakur
1
lengthMyślę, że tylko plik jest tylko do odczytu. Próbuję usunąć element ze złączem, w Chrome nie działa.
zhiyelee
Czy jest jakiś sposób, aby to dodać?
latarnia
1
@streetlight Byłaby to ogromna luka w zabezpieczeniach, gdyby właściciel witryny mógł określić, które pliki przesłać z komputera użytkownika.
Marcel Korpel
29

To pytanie zostało już oznaczone jako odpowiedź, ale chciałbym podzielić się pewnymi informacjami, które mogą pomóc innym w korzystaniu z FileList.

Byłoby wygodnie traktować FileList jako tablicę, ale metody takie jak sort, shift, pop i slice nie działają. Jak sugerowali inni, możesz skopiować FileList do tablicy. Jednak zamiast używać pętli, istnieje proste rozwiązanie jednokreskowe do obsługi tej konwersji.

 // fileDialog.files is a FileList 

 var fileBuffer=[];

 // append the file list to an array
 Array.prototype.push.apply( fileBuffer, fileDialog.files ); // <-- here

 // And now you may manipulated the result as required

 // shift an item off the array
 var file = fileBuffer.shift(0,1);  // <-- works as expected
 console.info( file.name + ", " + file.size + ", " + file.type );

 // sort files by size
 fileBuffer.sort(function(a,b) {
    return a.size > b.size ? 1 : a.size < b.size ? -1 : 0;
 });

Przetestowano poprawnie w FF, Chrome i IE10 +

Roberto
źródło
4
Array.from(fileDialog.files)jest prostsze
Muhammad Umer
1
@Muhammad Umer - Dziękuję, zgadzam się, że jest prostszy i wymieniony jako alternatywna odpowiedź. Zależy to jednak od tego, które przeglądarki trzeba obsługiwać i czy wymagają one wypełnienia pollyfill, aby użyć Array.from (). Zobacz: stackoverflow.com/a/36810954/943435
Roberto
Jak właściwie modyfikujesz FileList? Czy przypisać tę nową tablicę do wejścia fileDialog.files = fileBuffer ?
eozzy
@ 3zzy - Można modyfikować FileList, ale tylko w nowoczesnych przeglądarkach. Zobacz te pytania SO, aby uzyskać szczegółowe informacje: stackoverflow.com/a/47522812/943435
Roberto
22

Jeśli celujesz w wiecznie zielone przeglądarki (Chrome, Firefox, Edge, ale działa również w Safari 9+) lub możesz sobie pozwolić na polyfill, możesz zmienić FileList w tablicę, używając w Array.from()ten sposób:

let fileArray = Array.from(fileList);

Wtedy łatwo jest obsługiwać tablicę Files jak każdą inną tablicę.

adlr0
źródło
Idealny! Czy wiesz, jak o obsłudze IE? A może możesz udostępnić link do polyfillu?
Serhii Matrunchyk
Nie próbowałem, ale to pierwszy wynik w Google;) github.com/mathiasbynens/Array.from
adlr0
Będzie tylko pozwalają obsłużyć fileArraynie fileList.
VipinKundal
12

Ponieważ jesteśmy w dziedzinie HTML5, to jest moje rozwiązanie. Istota polega na tym, że przesyłasz pliki do Array zamiast pozostawiać je w FileList, a następnie za pomocą XHR2 wypychasz pliki do obiektu FormData. Przykład poniżej.

Node.prototype.replaceWith = function(node)
{
    this.parentNode.replaceChild(node, this);
};
if(window.File && window.FileList)
{
    var topicForm = document.getElementById("yourForm");
    topicForm.fileZone = document.getElementById("fileDropZoneElement");
    topicForm.fileZone.files = new Array();
    topicForm.fileZone.inputWindow = document.createElement("input");
    topicForm.fileZone.inputWindow.setAttribute("type", "file");
    topicForm.fileZone.inputWindow.setAttribute("multiple", "multiple");
    topicForm.onsubmit = function(event)
    {
        var request = new XMLHttpRequest();
        if(request.upload)
        {
            event.preventDefault();
            topicForm.ajax.value = "true";
            request.upload.onprogress = function(event)
            {
                var progress = event.loaded.toString() + " bytes transfered.";
                if(event.lengthComputable)
                progress = Math.round(event.loaded / event.total * 100).toString() + "%";
                topicForm.fileZone.innerHTML = progress.toString();
            };
            request.onload = function(event)
            {
                response = JSON.parse(request.responseText);
                // Handle the response here.
            };
            request.open(topicForm.method, topicForm.getAttribute("action"), true);
            var data = new FormData(topicForm);
            for(var i = 0, file; file = topicForm.fileZone.files[i]; i++)
                data.append("file" + i.toString(), file);
            request.send(data);
        }
    };
    topicForm.fileZone.firstChild.replaceWith(document.createTextNode("Drop files or click here."));
    var handleFiles = function(files)
    {
        for(var i = 0, file; file = files[i]; i++)
            topicForm.fileZone.files.push(file);
    };
    topicForm.fileZone.ondrop = function(event)
    {
        event.stopPropagation();
        event.preventDefault();
        handleFiles(event.dataTransfer.files);
    };
    topicForm.fileZone.inputWindow.onchange = function(event)
    {
        handleFiles(topicForm.fileZone.inputWindow.files);
    };
    topicForm.fileZone.ondragover = function(event)
    {
        event.stopPropagation();
        event.preventDefault();
    };
    topicForm.fileZone.onclick = function()
    {
        topicForm.fileZone.inputWindow.focus();
        topicForm.fileZone.inputWindow.click();
    };
}
else
    topicForm.fileZone.firstChild.replaceWith(document.createTextNode("It's time to update your browser."));
Joshua W.
źródło
Ajax to jedyny sposób, więc myślę?
Muhammad Umer
10

Znalazłem bardzo szybkie i krótkie obejście tego problemu. Przetestowane w wielu popularnych przeglądarkach (Chrome, Firefox, Safari);

Najpierw musisz przekonwertować FileList na Array

var newFileList = Array.from(event.target.files);

aby usunąć określony element, użyj tego

newFileList.splice(index,1);
MeVimalkumar
źródło
12
Utworzyłeś nową zmienną, event.target.filesktóra nie jest połączona z danymi wejściowymi, więc nie może zmienić niczego oprócz zmiennej lokalnej.
Maksims Kitajevs
6

Wiem, że to stare pytanie, ale w związku z tym problemem zajmuje wysokie miejsce w wyszukiwarkach.

właściwości w obiekcie FileList nie można usunąć, ale przynajmniej w przeglądarce Firefox można je zmienić . Moim obejściem tego problemu było dodanie właściwości IsValid=truedo tych plików, które przeszły sprawdzanie, i IsValid=falsedo tych, które nie.

następnie po prostu przeglądam listę, aby upewnić się, że tylko właściwości z IsValid=truesą dodawane do FormData .

A. Richards
źródło
formdata, więc wysyłasz je przez ajax?
Muhammad Umer
1

Może istnieć bardziej elegancki sposób, ale oto moje rozwiązanie. Dzięki Jquery

fileEle.value = "";
var parEle = $(fileEle).parent();
var newEle = $(fileEle).clone()
$(fileEle).remove();
parEle.append(newEle);

Zasadniczo kasujesz wartość wejścia. Sklonuj go i umieść klona w miejscu starego.

Nicholas Anderson
źródło
1

To ekstrawaganckie, ale miałem ten sam problem, który rozwiązałem w ten sposób. W moim przypadku przesyłałem pliki za pośrednictwem żądania XMLHttp, więc mogłem opublikować sklonowane dane FileList poprzez dołączanie danych formularza. Funkcjonalność polega na tym, że możesz przeciągać i upuszczać lub wybierać wiele plików tyle razy, ile chcesz (ponowne wybranie plików nie spowoduje zresetowania sklonowanej listy plików), usunąć dowolny plik z (sklonowanej) listy plików i przesłać przez xmlhttprequest cokolwiek było pozostawione tam. To właśnie zrobiłem. To mój pierwszy post, więc kod jest trochę niechlujny. Przepraszam. Ach, i musiałem użyć jQuery zamiast $, jak to było w skrypcie Joomla.

// some global variables
var clon = {};  // will be my FileList clone
var removedkeys = 0; // removed keys counter for later processing the request
var NextId = 0; // counter to add entries to the clone and not replace existing ones

jQuery(document).ready(function(){
    jQuery("#form input").change(function () {

    // making the clone
    var curFiles = this.files;
    // temporary object clone before copying info to the clone
    var temparr = jQuery.extend(true, {}, curFiles);
    // delete unnecessary FileList keys that were cloned
    delete temparr["length"];
    delete temparr["item"];

    if (Object.keys(clon).length === 0){
       jQuery.extend(true, clon, temparr);
    }else{
       var keysArr = Object.keys(clon);
       NextId = Math.max.apply(null, keysArr)+1; // FileList keys are numbers
       if (NextId < curFiles.length){ // a bug I found and had to solve for not replacing my temparr keys...
          NextId = curFiles.length;
       }
       for (var key in temparr) { // I have to rename new entries for not overwriting existing keys in clon
          if (temparr.hasOwnProperty(key)) {
             temparr[NextId] = temparr[key];
             delete temparr[key];
                // meter aca los cambios de id en los html tags con el nuevo NextId
                NextId++;
          }
       } 
       jQuery.extend(true, clon, temparr); // copy new entries to clon
    }

// modifying the html file list display

if (NextId === 0){
    jQuery("#filelist").html("");
    for(var i=0; i<curFiles.length; i++) {
        var f = curFiles[i];
        jQuery("#filelist").append("<p id=\"file"+i+"\" style=\'margin-bottom: 3px!important;\'>" + f.name + "<a style=\"float:right;cursor:pointer;\" onclick=\"BorrarFile("+i+")\">x</a></p>"); // the function BorrarFile will handle file deletion from the clone by file id
    }
}else{
    for(var i=0; i<curFiles.length; i++) {
        var f = curFiles[i];
        jQuery("#filelist").append("<p id=\"file"+(i+NextId-curFiles.length)+"\" style=\'margin-bottom: 3px!important;\'>" + f.name + "<a style=\"float:right;cursor:pointer;\" onclick=\"BorrarFile("+(i+NextId-curFiles.length)+")\">x</a></p>"); // yeap, i+NextId-curFiles.length actually gets it right
    }        
}
// update the total files count wherever you want
jQuery("#form p").text(Object.keys(clon).length + " file(s) selected");
    });
});

function BorrarFile(id){ // handling file deletion from clone
    jQuery("#file"+id).remove(); // remove the html filelist element
    delete clon[id]; // delete the entry
    removedkeys++; // add to removed keys counter
    if (Object.keys(clon).length === 0){
        jQuery("#form p").text(Object.keys(clon).length + " file(s) selected");
        jQuery("#fileToUpload").val(""); // I had to reset the form file input for my form check function before submission. Else it would send even though my clone was empty
    }else{
        jQuery("#form p").text(Object.keys(clon).length + " file(s) selected");
    }
}
// now my form check function

function check(){
    if( document.getElementById("fileToUpload").files.length == 0 ){
        alert("No file selected");
        return false;
    }else{
        var _validFileExtensions = [".pdf", ".PDF"]; // I wanted pdf files
        // retrieve input files
        var arrInputs = clon;

       // validating files
       for (var i = 0; i < Object.keys(arrInputs).length+removedkeys; i++) {
         if (typeof arrInputs[i]!="undefined"){
           var oInput = arrInputs[i];
           if (oInput.type == "application/pdf") {
               var sFileName = oInput.name;
               if (sFileName.length > 0) {
                   var blnValid = false;
                   for (var j = 0; j < _validFileExtensions.length; j++) {
                     var sCurExtension = _validFileExtensions[j];
                     if (sFileName.substr(sFileName.length - sCurExtension.length, sCurExtension.length).toLowerCase() == sCurExtension.toLowerCase()) {
                       blnValid = true;
                       break;
                     }
                   }
                  if (!blnValid) {
                    alert("Sorry, " + sFileName + " is invalid, allowed extensions are: " + _validFileExtensions.join(", "));
                    return false;
                  }
              }
           }else{
           alert("Sorry, " + arrInputs[0].name + " is invalid, allowed extensions are: " + _validFileExtensions.join(" or "));
           return false;
           }
         }
       }

    // proceed with the data appending and submission
    // here some hidden input values i had previously set. Now retrieving them for submission. My form wasn't actually even a form...
    var fecha = jQuery("#fecha").val();
    var vendor = jQuery("#vendor").val();
    var sku = jQuery("#sku").val();
    // create the formdata object
    var formData = new FormData();
    formData.append("fecha", fecha);
    formData.append("vendor", encodeURI(vendor));
    formData.append("sku", sku);
    // now appending the clone file data (finally!)
    var fila = clon; // i just did this because I had already written the following using the "fila" object, so I copy my clone again
    // the interesting part. As entries in my clone object aren't consecutive numbers I cannot iterate normally, so I came up with the following idea
    for (i = 0; i < Object.keys(fila).length+removedkeys; i++) { 
        if(typeof fila[i]!="undefined"){
            formData.append("fileToUpload[]", fila[i]); // VERY IMPORTANT the formdata key for the files HAS to be an array. It will be later retrieved as $_FILES['fileToUpload']['temp_name'][i]
        }
    }
    jQuery("#submitbtn").fadeOut("slow"); // remove the upload btn so it can't be used again
    jQuery("#drag").html(""); // clearing the output message element
    // start the request
    var xhttp = new XMLHttpRequest();
    xhttp.addEventListener("progress", function(e) {
            var done = e.position || e.loaded, total = e.totalSize || e.total;
        }, false);
        if ( xhttp.upload ) {
            xhttp.upload.onprogress = function(e) {
                var done = e.position || e.loaded, total = e.totalSize || e.total;
                var percent = done / total;
                jQuery("#drag").html(Math.round(percent * 100) + "%");
            };
        }
      xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
         var respuesta = this.responseText;
         jQuery("#drag").html(respuesta);
        }
      };
      xhttp.open("POST", "your_upload_handler.php", true);  
      xhttp.send(formData);
    return true;
    }
};

Teraz HTML i style do tego. Jestem całkiem nowicjuszem, ale wszystko to działało i zajęło mi trochę czasu, zanim to rozgryzłem.

<div id="form" class="formpos">
<!--    Select the pdf to upload:-->
  <input type="file" name="fileToUpload[]" id="fileToUpload" accept="application/pdf" multiple>
  <div><p id="drag">Drop your files here or click to select them</p>
  </div>
  <button id="submitbtn" onclick="return check()" >Upload</button>
// these inputs are passed with different names on the formdata. Be aware of that
// I was echoing this, so that's why I use the single quote for php variables
  <input type="hidden" id="fecha" name="fecha_copy" value="'.$fecha.'" />
  <input type="hidden" id="vendor" name="vendorname" value="'.$vendor.'" />
  <input type="hidden" id="sku" name="sku" value="'.$sku.'"" />
</div>
<h1 style="width: 500px!important;margin:20px auto 0px!important;font-size:24px!important;">File list:</h1>
<div id="filelist" style="width: 500px!important;margin:10px auto 0px!important;">Nothing selected yet</div>

Style do tego. Musiałem oznaczyć niektóre z nich! Ważne, aby zastąpić zachowanie Joomla.

.formpos{
  width: 500px;
  height: 200px;
  border: 4px dashed #999;
  margin: 30px auto 100px;
 }
.formpos  p{
  text-align: center!important;
  padding: 80px 30px 0px;
  color: #000;
}
.formpos  div{
  width: 100%!important;
  height: 100%!important;
  text-align: center!important;
  margin-bottom: 30px!important;
}
.formpos input{
  position: absolute!important;
  margin: 0!important;
  padding: 0!important;
  width: 500px!important;
  height: 200px!important;
  outline: none!important;
  opacity: 0!important;
}
.formpos button{
  margin: 0;
  color: #fff;
  background: #16a085;
  border: none;
  width: 508px;
  height: 35px;
  margin-left: -4px;
  border-radius: 4px;
  transition: all .2s ease;
  outline: none;
}
.formpos button:hover{
  background: #149174;
  color: #0C5645;
}
.formpos button:active{
  border:0;
}

Mam nadzieję, że to pomoże.

Eric
źródło
1

Dzięki @Nicholas Anderson prosto i prosto, oto twój kod zastosowany i działający na moim kodzie za pomocą jquery.

HTML.

<input class="rangelog btn border-aero" id="file_fr" name="file_fr[]" multiple type="file" placeholder="{$labels_helpfiles_placeholder_file}">
<span style="cursor: pointer; cursor: hand;" onclick="cleanInputs($('#file_fr'))"><i class="fa fa-trash"></i> Empty chosen files</span>

KOD JS

   function cleanInputs(fileEle){
    $(fileEle).val("");
    var parEle = $(fileEle).parent();
    var newEle = $(fileEle).clone()
    $(fileEle).remove();
    $(parEle).prepend(newEle);
}
Sultanos
źródło
1

W Vue js:

self.$refs.inputFile.value = ''

Pragati Dugar
źródło
0

Jeśli masz szczęście, że wysyłasz żądanie do bazy danych z plikami i masz pliki, które chcesz wysłać w swoim DOM

możesz po prostu sprawdzić, czy plik z listy plików jest obecny w Twoim DOM, a jeśli nie, po prostu nie wysyłaj tego elementu do de DB.

Neku80
źródło
-1

Możesz chcieć utworzyć tablicę i użyć jej zamiast listy plików tylko do odczytu.

var myReadWriteList = new Array();
// user selects files later...
// then as soon as convenient... 
myReadWriteList = FileListReadOnly;

Następnie prześlij do swojej listy zamiast wbudowanej listy. Nie jestem pewien kontekstu, w którym pracujesz, ale pracuję z wtyczką jquery, którą znalazłem i co musiałem zrobić, to pobrać źródło wtyczki i umieścić ją na stronie za pomocą <script>tagów. Następnie nad źródłem dodałem swoją tablicę, aby mogła działać jako zmienna globalna, a wtyczka mogła się do niej odwoływać.

Potem była już tylko kwestia zamiany odniesień.

Myślę, że pozwoliłoby ci to również dodać przeciągnij i upuść, tak jak ponownie, jeśli wbudowana lista jest tylko do odczytu, to jak inaczej możesz umieścić upuszczone pliki na liście?

:))

cary abramoff
źródło
4
Napisałem zbyt wcześnie ... wygląda na to, że w momencie, gdy ustawia się zmienną na równą filelist, wraca problem tylko do odczytu .... Tak więc wybrałem dwa razy i trochę bolesny, ale skuteczny ... zachowuję widoczna lista plików do przesłania i stąd użytkownik może usunąć ... oczywiście usunięcie tagu <li> w tagu <ul> jest proste ... więc jedyną metodą, którą wymyśliłem, jest zachowanie dodatkowej listy usuniętych plików i odwołuję się do niego podczas przesyłania ... dlatego jeśli plik znajduje się na liście przesyłania, po prostu go pomijam, a użytkownik nie jest mądrzejszy.
Cary Abramoff
Kiedy przypisujesz FileListobiekt do myReadWriteListzmiennej, zmienia on jego typ z Arrayna FileList, więc nie jest to rozwiązanie.
adlr0
-2

Po prostu zmieniam typ wejścia na tekst iz powrotem do pliku: D

maLikiz
źródło
Uważa się to za komentarz
Ivan Kaloyanov
Jak ma działać? Jak to osiągnąłeś?
Ulrich Dohou