Como faço para remover um arquivo da FileList

110

Estou construindo um aplicativo da web de arrastar e soltar para fazer upload usando HTML5 e soltando os arquivos em um div e, claro, buscando o objeto dataTransfer, que me dá a FileList .

Agora quero remover alguns dos arquivos, mas não sei como, nem se é possível.

De preferência, gostaria apenas de excluí-los da FileList; Eu não tenho uso para eles. Mas se isso não for possível, devo escrever em cheques no código que interage com a FileList? Isso parece complicado.

Heilemann
fonte
Só por curiosidade: por que você quer fazer isso? Por que você diz “Não tenho uso para eles” sobre (alguns) arquivos que o usuário selecionou?
Marcel Korpel 01 de
23
Provavelmente é mais para que o usuário possa remover os arquivos antes de fazer o upload. Se você selecionou originalmente 20 e decidiu que realmente não deseja carregar o 14, então você não pode simplesmente remover aquele, você tem que começar tudo de novo (o que é um pouco chato). Acho que tornar FileList somente leitura é um descuido, a menos que haja alguma implicação de segurança que não estou vendo.
Rafael
São problemas de segurança com a exclusão de arquivos do FileList de entrada diretamente, mas você pode clonar esse FileList imediatamente após fechar a caixa de diálogo de upload de arquivo e, em seguida, modificar este clone e usá-lo ao postar via ajax
alex_1948511

Respostas:

147

Se você quiser excluir apenas alguns dos arquivos selecionados: você não pode. O Rascunho de trabalho da API de arquivo ao qual você vinculou contém uma nota:

A HTMLInputElementinterface [HTML5] tem um atributo somente leitura FileList , [...]
[ênfase minha]

Lendo um pouco do HTML 5 Working Draft, me deparei com as APIs de elemento comuminput . Parece que você pode excluir toda a lista de arquivos definindo a valuepropriedade do inputobjeto como uma string vazia, como:

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

BTW, o artigo Usando arquivos de aplicativos da web também pode ser interessante.

Marcel Korpel
fonte
1
Observe que um atributo sendo somente leitura não significa que você não pode alterar o objeto para o qual ele aponta. Você poderia manipular FileList (se isso fosse possível), isso significa apenas que você não pode atribuir um novo FileList a ele.
Robin Berjon
1
@RobinBerjon Chrome parece ignorar o atributo ´readonly´ enquanto o FireFox não permite operações de gravação. Infelizmente, sua sugestão de apenas manipular o FileList também não funciona no FireFox.
borisdiakur
1
Apenas o lengthé somente leitura, eu acho. Tento excluir um item com emenda, mas falha no Chrome.
zhiyelee
Existe alguma maneira de adicionar?
poste de
1
@streetlight Isso seria uma grande vulnerabilidade de segurança, se o proprietário do site pudesse determinar quais arquivos carregar da máquina de um usuário.
Marcel Korpel de
29

Esta pergunta já foi marcada como respondida, mas gostaria de compartilhar algumas informações que podem ajudar outras pessoas a usar o FileList.

Seria conveniente tratar um FileList como um array, mas métodos como sort, shift, pop e slice não funcionam. Como outros sugeriram, você pode copiar FileList para um array. No entanto, em vez de usar um loop, há uma solução simples de uma linha para lidar com essa conversão.

 // 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;
 });

Testado OK em FF, Chrome e IE10 +

Roberto
fonte
4
Array.from(fileDialog.files)é mais simples
Muhammad Umer
1
@Muhammad Umer - Obrigado, concordo que é mais simples e está listado como uma resposta alternativa. Ainda assim, depende de quais navegadores devem ser suportados e se eles exigem um pollyfill para usar Array.from (). Consulte: stackoverflow.com/a/36810954/943435
Roberto
Como você realmente modifica o FileList? Atribuir esta nova matriz à entrada fileDialog.files = fileBuffer ?
eozzy de
@ 3zzy - É possível modificar FileList, mas apenas em navegadores modernos. Consulte estas perguntas do SO para obter detalhes: stackoverflow.com/a/47522812/943435
Roberto
22

Se você está almejando navegadores perenes (Chrome, Firefox, Edge, mas também funciona no Safari 9+) ou pode pagar um polyfill, você pode transformar o FileList em uma matriz usando Array.from()desta forma:

let fileArray = Array.from(fileList);

Então, é fácil lidar com o array de Files como qualquer outro array.

adlr0
fonte
Perfeito! Você sabe como é o suporte do IE? Ou talvez você possa compartilhar um link para um polyfill?
Serhii Matrunchyk
Eu não tentei, mas este é o primeiro resultado do Google;) github.com/mathiasbynens/Array.from
adlr0
Isso só vai permitir que você fileArraynão controle fileList.
VipinKundal
12

Já que estamos no reino do HTML5, esta é a minha solução. A essência é que você envia os arquivos para um Array em vez de deixá-los em uma FileList e, em seguida, usando o XHR2, envia os arquivos para um objeto FormData. Exemplo abaixo.

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
fonte
Ajax é a única maneira, então eu acho?
Muhammad Umer
10

Eu encontrei uma solução muito rápida e curta para isso. Testado em muitos navegadores populares (Chrome, Firefox, Safari);

Primeiro, você deve converter FileList em um Array

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

para deletar o elemento particular, use este

newFileList.splice(index,1);
MeVimalkumar
fonte
12
Você criou uma nova variável a partir da event.target.filesqual não está vinculada à entrada, portanto, não pode alterar nada, exceto sua variável local ..
Maksims Kitajevs
6

Eu sei que esta é uma pergunta antiga, mas ela está bem posicionada nos mecanismos de pesquisa em relação a esse problema.

as propriedades no objeto FileList não podem ser excluídas, mas pelo menos no Firefox elas podem ser alteradas . Minha solução para esse problema foi adicionar uma propriedade IsValid=trueaos arquivos que passaram na verificação e IsValid=falseaos que não passaram.

em seguida, simplesmente percorro a lista para ter certeza de que apenas as propriedades com IsValid=truesão adicionadas a FormData .

A. Richards
fonte
formdata, então você os envia por meio de ajax?
Muhammad Umer
1

Pode haver uma maneira mais elegante de fazer isso, mas aqui está a minha solução. Com Jquery

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

Basicamente, você determina o valor da entrada. Clone-o e coloque o clone no lugar do antigo.

Nicholas Anderson
fonte
1

Isso é extemporâneo, mas tive o mesmo problema que resolvi dessa forma. No meu caso, eu estava carregando os arquivos por meio de uma solicitação XMLHttp, então pude postar os dados clonados de FileList por meio da anexação de dados de formulário. A funcionalidade é que você pode arrastar e soltar ou selecionar vários arquivos quantas vezes quiser (selecionar arquivos novamente não redefinirá a FileList clonada), remover qualquer arquivo que desejar da lista de arquivos (clonados) e enviar via xmlhttprequest o que quer que fosse deixou lá. Isso é o que eu fiz. É meu primeiro post aqui, então o código é um pouco confuso. Desculpe. Ah, e eu tive que usar jQuery em vez de $ como era no script do 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;
    }
};

Agora, o html e os estilos para isso. Eu sou um novato, mas tudo isso realmente funcionou para mim e levei um tempo para descobrir.

<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>

Os estilos para isso. Eu tive que marcar alguns deles! Importantes para substituir o comportamento do 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;
}

Eu espero que isso ajude.

Eric
fonte
1

Obrigado @Nicholas Anderson simples e direto, aqui está seu código aplicado e trabalhando no meu código usando 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>

CÓDIGO JS

   function cleanInputs(fileEle){
    $(fileEle).val("");
    var parEle = $(fileEle).parent();
    var newEle = $(fileEle).clone()
    $(fileEle).remove();
    $(parEle).prepend(newEle);
}
Sultanos
fonte
1

In vue js:

self.$refs.inputFile.value = ''

Pragati Dugar
fonte
0

Se você tiver a sorte de estar enviando uma solicitação de postagem para o banco de dados com os arquivos e tiver os arquivos que deseja enviar em seu DOM

você pode simplesmente verificar se o arquivo na lista de arquivos está presente em seu DOM e, claro, se não estiver, simplesmente não envie esse elemento para o banco de dados.

Neku80
fonte
-1

Você pode desejar criar uma matriz e usá-la em vez da lista de arquivos somente leitura.

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

Depois desse ponto, faça o upload na sua lista em vez de na lista incorporada. Não tenho certeza do contexto em que você está trabalhando, mas estou trabalhando com um plugin jquery que encontrei e o que tive que fazer foi pegar o código-fonte do plugin e colocá-lo na página usando <script>tags. Então, acima do código-fonte, adicionei meu array para que ele possa atuar como uma variável global e o plugin possa fazer referência a ele.

Depois, foi só trocar as referências.

Eu acho que isso permitiria que você também adicionasse arrastar e soltar novamente, se a lista embutida for somente leitura, então de que outra forma você poderia colocar os arquivos soltos na lista?

:))

Cary Abramoff
fonte
4
Escrevi muito cedo ... parece que no momento em que se define uma var para igualar a lista de arquivos, o problema somente leitura volta ... Assim, o que escolhi fazer é duplo e um pouco doloroso, mas eficaz ... Eu mantenho uma lista visível de arquivos para upload e daqui o usuário pode remover ... obviamente, remover uma tag <li> em uma tag <ul> é simples ... então o único método que eu criei é manter uma lista secundária de arquivos removidos e consultá-los durante o processo de upload ... portanto, se o arquivo está na lista de upload, eu simplesmente pulo e o usuário não percebe.
cary abramoff
Quando você atribui o FileListobjeto à myReadWriteListvariável, ele muda seu tipo de Arraypara FileList, portanto, esta não é uma solução.
adlr0
-2

Acabei de mudar o tipo de entrada para o texto e de volta para o arquivo: D

maLikiz
fonte
Isso é considerado um comentário
Ivan Kaloyanov
Como deve funcionar? Como você conseguiu isso?
Ulrich Dohou 01 de