¿Cómo elimino un archivo de FileList?

111

Estoy construyendo una aplicación web de arrastrar y soltar para cargar usando HTML5, y estoy colocando los archivos en un div y, por supuesto, obteniendo el objeto dataTransfer, que me da FileList .

Ahora quiero eliminar algunos de los archivos, pero no sé cómo, ni siquiera si es posible.

Preferiblemente me gustaría eliminarlos de FileList; No tengo ningún uso para ellos. Pero si eso no es posible, ¿debería escribir comprobaciones en el código que interactúa con FileList? Eso parece engorroso.

Heilemann
fuente
Solo curiosidad: ¿por qué quieres hacer esto? ¿Por qué dice "No tengo ningún uso para ellos" sobre (algunos) archivos que el usuario seleccionó?
Marcel Korpel
23
Probablemente sea más para que el usuario pueda eliminar archivos antes de cargarlos. Si originalmente seleccionó 20 y luego decide que en realidad no desea cargar el 14, entonces no puede simplemente eliminar ese, debe comenzar de nuevo (lo cual es un poco molesto). Creo que hacer que FileList sea de solo lectura es un mal descuido, a menos que haya alguna implicación de seguridad que no esté viendo.
Rafael
Son problemas de seguridad eliminar archivos de la lista de archivos de entrada directamente, pero puede clonar esa lista de archivos inmediatamente después de cerrar el cuadro de diálogo de carga de archivos y luego modificar este clon y usarlo al publicar a través de ajax
alex_1948511

Respuestas:

147

Si desea eliminar solo varios de los archivos seleccionados: no puede. El Borrador de trabajo de la API de archivos al que vinculó contiene una nota:

La HTMLInputElementinterfaz [HTML5] tiene un atributo de solo lectura FileList , […]
[énfasis mío]

Leyendo un poco del Borrador de trabajo de HTML 5, encontré las API de elementos comunesinput . Parece que puede eliminar toda la lista de archivos configurando elvalue propiedad del inputobjeto en una cadena vacía, como:

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

Por cierto, el artículo Uso de archivos de aplicaciones web también puede ser de interés.

Marcel Korpel
fuente
1
Tenga en cuenta que un atributo de solo lectura no significa que no pueda cambiar el objeto al que apunta. Puede manipular FileList (si eso fuera posible), solo significa que no puede asignarle una nueva FileList.
Robin Berjon
1
@RobinBerjon Chrome parece ignorar el atributo ´readonly´ mientras que FireFox no permite operaciones de escritura. Desafortunadamente, su sugerencia de simplemente manipular FileList tampoco funciona en FireFox.
borisdiakur
1
lengthCreo que solo es de solo lectura. Intento eliminar un elemento con empalme, falla en Chrome.
zhiyelee
¿Hay alguna forma de agregar?
farola
1
@streetlight Eso sería una gran vulnerabilidad de seguridad, si el propietario del sitio puede determinar qué archivos cargar desde la máquina de un usuario.
Marcel Korpel
29

Esta pregunta ya se marcó como respondida, pero me gustaría compartir información que podría ayudar a otros a usar FileList.

Sería conveniente tratar FileList como una matriz, pero los métodos como sort, shift, pop y slice no funcionan. Como han sugerido otros, puede copiar FileList en una matriz. Sin embargo, en lugar de usar un bucle, existe una solución simple de una línea para manejar esta conversión.

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

Probado bien en FF, Chrome e IE10 +

Roberto
fuente
4
Array.from(fileDialog.files)es más simple
Muhammad Umer
1
@Muhammad Umer - Gracias, estoy de acuerdo en que es más simple y aparece como una respuesta alternativa. Sin embargo, depende de qué navegadores se deben admitir y si requieren un pollyfill para usar Array.from (). Ver: stackoverflow.com/a/36810954/943435
Roberto
¿Cómo se modifica realmente FileList? ¿Asignar esta nueva matriz a la entrada fileDialog.files = fileBuffer ?
Eozzy
@ 3zzy: es posible modificar FileList, pero solo en los navegadores modernos. Consulte estas preguntas de SO para obtener más detalles: stackoverflow.com/a/47522812/943435
Roberto
22

Si está apuntando a navegadores de hoja perenne (Chrome, Firefox, Edge, pero también funciona en Safari 9+) o puede permitirse un polyfill, puede convertir FileList en una matriz usando Array.from()así:

let fileArray = Array.from(fileList);

Entonces es fácil manejar la matriz de Files como cualquier otra matriz.

adlr0
fuente
¡Perfecto! ¿Sabes qué hay del soporte de IE? ¿O quizás puedas compartir un enlace a un polyfill?
Serhii Matrunchyk
No lo he probado, pero este es el primer resultado de Google;) github.com/mathiasbynens/Array.from
adlr0
Sólo se permitirá manejar fileArrayno fileList.
VipinKundal
12

Dado que estamos en el ámbito de HTML5, esta es mi solución. La esencia es que empuja los archivos a una matriz en lugar de dejarlos en una lista de archivos, luego, utilizando XHR2, empuja los archivos a un objeto FormData. Ejemplo a continuación.

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
fuente
¿Ajax es la única forma entonces supongo?
Muhammad Umer
10

He encontrado una solución muy rápida y breve para esto. Probado en muchos navegadores populares (Chrome, Firefox, Safari);

Primero, debe convertir FileList en una matriz

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

para eliminar el elemento particular use esto

newFileList.splice(index,1);
MeVimalkumar
fuente
12
Creó una nueva variable desde la event.target.filescual no está vinculada a la entrada, por lo que no puede cambiar nada excepto su variable local ..
Maksims Kitajevs
6

Sé que esta es una pregunta antigua, pero ocupa un lugar destacado en los motores de búsqueda con respecto a este tema.

las propiedades en el objeto FileList no se pueden eliminar, pero al menos en Firefox se pueden cambiar . Mi solución a este problema fue agregar una propiedad IsValid=truea los archivos que pasaron la verificación yIsValid=false a los que no.

luego simplemente recorro la lista para asegurarme de que solo las propiedades con IsValid=truese agreguen a FormData .

A. Richards
fuente
formdata, entonces los envías a través de ajax?
Muhammad Umer
1

Puede haber una forma más elegante de hacer esto, pero aquí está mi solución. Con Jquery

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

Básicamente, fijas el valor de la entrada. Clone y coloque el clon en lugar del anterior.

Nicholas Anderson
fuente
1

Esto es extemporáneo, pero tuve el mismo problema que resolví de esta manera. En mi caso, estaba cargando los archivos a través de la solicitud XMLHttp, por lo que pude publicar los datos clonados de FileList mediante la adición de formdata. La funcionalidad es que puede arrastrar y soltar o seleccionar varios archivos tantas veces como desee (seleccionar archivos de nuevo no restablecerá la lista de archivos clonada), eliminar cualquier archivo que desee de la lista de archivos (clonados) y enviar a través de xmlhttprequest lo que sea dejado allí. Esto es lo que hice. Es mi primera publicación aquí, por lo que el código es un poco complicado. Lo siento. Ah, y tuve que usar jQuery en lugar de $ como estaba en el script de 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;
    }
};

Ahora el HTML y los estilos para esto. Soy bastante novato, pero todo esto realmente funcionó para mí y me tomó un tiempo resolverlo.

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

Los estilos para eso. Tuve que marcar algunos de ellos! Importantes para anular el comportamiento de 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;
}

Espero que esto ayude.

Eric
fuente
1

Gracias @Nicholas Anderson simple y directo, aquí está su código aplicado y trabajando en mi 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
fuente
1

En vue js:

self.$refs.inputFile.value = ''

Pragati Dugar
fuente
0

Si tiene la suerte de enviar una solicitud de publicación a la base de datos con los archivos y tiene los archivos que desea enviar en su DOM

simplemente puede verificar si el archivo en la lista de archivos está presente en su DOM y, por supuesto, si no lo está, simplemente no envíe ese elemento a la base de datos.

Neku80
fuente
-1

Es posible que desee crear una matriz y usarla en lugar de la lista de archivos de solo lectura.

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

Después de ese punto, realice la carga contra su lista en lugar de la lista incorporada. No estoy seguro del contexto en el que está trabajando, pero estoy trabajando con un complemento de jquery que encontré y lo que tuve que hacer fue tomar la fuente del complemento y ponerlo en la página usando<script> etiquetas. Luego, por encima de la fuente, agregué mi matriz para que pueda actuar como una variable global y el complemento pueda hacer referencia a ella.

Entonces fue solo cuestión de intercambiar las referencias.

Creo que esto le permitiría también agregar arrastrar y soltar como nuevamente, si la lista incorporada es de solo lectura, ¿de qué otra manera podría obtener los archivos soltados en la lista?

:))

cary abramoff
fuente
4
Escribí demasiado pronto ... parece que en el momento en que uno establece una var para igualar la lista de archivos, el problema de solo lectura vuelve ... Por lo tanto, lo que he elegido hacer es doble y un poco doloroso pero efectivo ... una lista visible de archivos para cargar y desde aquí el usuario puede eliminar ... obviamente, eliminar una etiqueta <li> en una etiqueta <ul> es sencillo ... así que el único método que se me ocurrió es mantener una lista secundaria de archivos eliminados y consultarlo durante el proceso de carga ... por lo tanto, si el archivo está en la lista de carga, simplemente lo omito y el usuario no se da cuenta.
cary abramoff
Cuando asigna el FileListobjeto a la myReadWriteListvariable, cambia su tipo de Arraya FileList, por lo que esta no es una solución.
adlr0
-2

Solo cambio el tipo de entrada al texto y de nuevo al archivo: D

maLikiz
fuente
Esto se considera un comentario
Ivan Kaloyanov
¿Cómo se supone que funciona? ¿Cómo lograste eso?
Ulrich Dohou