Comment supprimer un fichier de la FileList


111

Je construis une application Web glisser-déposer pour télécharger en utilisant HTML5, et je dépose les fichiers sur un div et bien sûr récupère l'objet dataTransfer, ce qui me donne la FileList .

Maintenant, je veux supprimer certains fichiers, mais je ne sais pas comment ou si c'est même possible.

De préférence, je voudrais simplement les supprimer de la FileList; Je n'ai aucune utilité pour eux. Mais si ce n'est pas possible, dois-je plutôt écrire des chèques dans un code qui interagit avec FileList? Cela semble fastidieux.


Juste curieux: pourquoi voulez-vous faire cela? Pourquoi dites-vous «Je n'ai aucune utilité pour eux» à propos de (certains) fichiers sélectionnés par l'utilisateur?
Marcel Korpel

24
C'est probablement plus pour que l'utilisateur puisse supprimer des fichiers avant de les télécharger. Si vous aviez initialement sélectionné 20 et que vous décidez de ne pas télécharger le 14, vous ne pouvez pas simplement supprimer celui-ci, vous devez tout recommencer (ce qui est un peu pénible). Je pense que faire FileList en lecture seule est un mauvais oubli, à moins qu'il y ait une implication de sécurité que je ne vois pas.
Rafael du

C'est des problèmes de sécurité avec la suppression des fichiers de la FileList d'entrée directement, mais vous pouvez cloner cette FileList immédiatement après la fermeture de la boîte de dialogue de téléchargement de fichier, puis modifier ce clone et l'utiliser lors de la publication via ajax
alex_1948511

Réponses:


149

Si vous ne souhaitez supprimer que plusieurs des fichiers sélectionnés: vous ne pouvez pas. Le brouillon de travail de l'API de fichier auquel vous avez lié contient une note:

L' HTMLInputElementinterface [HTML5] a un attribut en lecture seule FileList , […]
[c'est moi qui souligne]

En lisant un peu le brouillon de travail HTML 5, je suis tombé sur les API des éléments communsinput . Il semble que vous pouvez supprimer toute la liste de fichiers en définissant la valuepropriété de l' inputobjet sur une chaîne vide, comme:

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

BTW, l'article Utilisation de fichiers à partir d'applications Web pourrait également être intéressant.


1
Notez qu'un attribut en lecture seule ne signifie pas que vous ne pouvez pas modifier l'objet vers lequel il pointe. Vous pouvez manipuler FileList (si cela était possible), cela signifie simplement que vous ne pouvez pas lui attribuer une nouvelle FileList.
Robin Berjon

1
@RobinBerjon Chrome semble ignorer l'attribut «lecture seule» alors que FireFox n'autorise pas les opérations d'écriture. Malheureusement, votre suggestion de simplement manipuler la FileList ne fonctionne pas non plus dans FireFox.
borisdiakur

1
Seul le lengthest en lecture seule, je pense. J'essaie de supprimer un élément avec une épissure, cela échoue dans Chrome.
zhiyelee

Y a-t-il moyen d'ajouter?
lampadaire

1
@streetlight Ce serait une énorme faille de sécurité si le propriétaire du site pouvait déterminer les fichiers à télécharger à partir de la machine d'un utilisateur.
Marcel Korpel

29

Cette question a déjà été marquée comme répondue, mais j'aimerais partager certaines informations qui pourraient aider d'autres personnes à utiliser FileList.

Il serait pratique de traiter une FileList comme un tableau, mais des méthodes telles que sort, shift, pop et slice ne fonctionnent pas. Comme d'autres l'ont suggéré, vous pouvez copier la FileList dans un tableau. Cependant, plutôt que d'utiliser une boucle, il existe une solution simple en une ligne pour gérer cette conversion.

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

Testé OK dans FF, Chrome et IE10 +


4
Array.from(fileDialog.files)is simpler
Muhammad Umer

1
@Muhammad Umer - Merci, je suis d'accord pour dire que c'est plus simple et qu'il est répertorié comme une réponse alternative. Pourtant, cela dépend des navigateurs à prendre en charge et s'ils nécessitent un pollyfill pour utiliser Array.from (). Voir: stackoverflow.com/a/36810954/943435
Roberto

Comment modifiez-vous réellement la FileList? Attribuer ce nouveau tableau à l'entrée fileDialog.files = fileBuffer ?
eozzy le

@ 3zzy - Il est possible de modifier FileList, mais uniquement dans les navigateurs modernes. Voir ces questions SO pour plus de détails: stackoverflow.com/a/47522812/943435
Roberto

22

Si vous ciblez des navigateurs à feuilles persistantes (Chrome, Firefox, Edge, mais fonctionne également dans Safari 9+) ou si vous pouvez vous permettre un polyfill, vous pouvez transformer la FileList en un tableau en utilisant Array.from()comme ceci:

let fileArray = Array.from(fileList);

Ensuite, il est facile de gérer le tableau de Files comme n'importe quel autre tableau.


Parfait! Savez-vous qu'en est-il du support IE? Ou peut-être pouvez-vous partager un lien vers un polyfill?
Serhii Matrunchyk

Je ne l'ai pas essayé mais c'est le premier résultat Google;) github.com/mathiasbynens/Array.from
adlr0

Cela vous permettra seulement de gérer fileArraynon fileList.
VipinKundal

12

Puisque nous sommes dans le domaine HTML5, c'est ma solution. L'essentiel est que vous poussez les fichiers vers un tableau au lieu de les laisser dans une FileList, puis en utilisant XHR2, vous poussez les fichiers vers un objet FormData. Exemple ci-dessous.

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."));

ajax est le seul moyen alors je suppose?
Muhammad Umer

10

J'ai trouvé une solution de contournement très rapide et courte pour cela. Testé dans de nombreux navigateurs populaires (Chrome, Firefox, Safari);

Tout d'abord, vous devez convertir FileList en un tableau

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

pour supprimer l'élément particulier, utilisez ceci

newFileList.splice(index,1);

12
Vous avez créé une nouvelle variable à partir de event.target.fileslaquelle n'est pas liée à l'entrée, donc elle ne peut rien changer sauf votre variable locale ..
Maksims Kitajevs

6

Je sais que c'est une vieille question, mais elle est bien classée dans les moteurs de recherche en ce qui concerne ce problème.

les propriétés de l' objet FileList ne peuvent pas être supprimées mais au moins sur Firefox, elles peuvent être modifiées . Ma solution de contournement à ce problème consistait à ajouter une propriété IsValid=trueaux fichiers qui ont passé la vérification et IsValid=falseà ceux qui ne l'ont pas été.

puis je viens de parcourir la liste pour m'assurer que seules les propriétés avec IsValid=truesont ajoutées à FormData .


formdata, alors vous les envoyez via ajax?
Muhammad Umer

1

Il y a peut-être une façon plus élégante de le faire, mais voici ma solution. Avec Jquery

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

En gros, vous calez la valeur de l'entrée. Clonez-le et mettez le clone à la place de l'ancien.


1

C'est improvisé, mais j'ai eu le même problème que j'ai résolu de cette façon. Dans mon cas, je téléchargeais les fichiers via une requête XMLHttp, j'ai donc pu publier les données clonées FileList via l'ajout de formdata. La fonctionnalité est que vous pouvez faire glisser et déposer ou sélectionner plusieurs fichiers autant de fois que vous le souhaitez (la sélection de fichiers à nouveau ne réinitialisera pas la FileList clonée), supprimez tout fichier de votre choix de la liste de fichiers (clonée) et soumettez via xmlhttprequest ce qui était laissé là. C'est ce que j'ai fait. C'est mon premier message ici, donc le code est un peu compliqué. Désolé. Ah, et j'ai dû utiliser jQuery au lieu de $ comme c'était le cas dans le script 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;
    }
};

Maintenant, le html et les styles pour cela. Je suis assez novice mais tout cela a fonctionné pour moi et il m'a fallu un certain temps pour le comprendre.

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

Les styles pour ça. J'ai dû en marquer certains! Important pour remplacer le comportement 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;
}

J'espère que ça aide.


1

Merci @Nicholas Anderson simple et direct, voici votre code appliqué et travaillant sur mon code en utilisant 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>

CODE JS

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


0

Si vous avez la chance d'envoyer une demande de publication à la base de données avec les fichiers et que vous avez les fichiers que vous souhaitez envoyer dans votre DOM

vous pouvez simplement vérifier si le fichier de la liste de fichiers est présent dans votre DOM, et bien sûr, si ce n'est pas le cas, n'envoyez pas cet élément à de DB.


-1

Vous souhaiterez peut-être créer un tableau et l'utiliser à la place de la liste de fichiers en lecture seule.

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

Après ce point, effectuez votre téléchargement par rapport à votre liste au lieu de la liste intégrée. Je ne suis pas sûr du contexte dans lequel vous travaillez mais je travaille avec un plugin jquery que j'ai trouvé et ce que je devais faire était de prendre la source du plugin et de le mettre dans la page en utilisant des <script>balises. Ensuite, au-dessus de la source, j'ai ajouté mon tableau afin qu'il puisse agir comme une variable globale et que le plugin puisse le référencer.

Ensuite, il s'agissait simplement d'échanger les références.

Je pense que cela vous permettrait également d'ajouter le glisser-déposer, si la liste intégrée est en lecture seule, comment pourriez-vous obtenir les fichiers déposés dans la liste?

:))


4
J'ai écrit trop tôt .... il semble qu'au moment où l'on règle une var pour égaler la liste de fichiers, le problème de lecture seule revient .... Ainsi, ce que j'ai choisi de faire est double et légèrement douloureux mais efficace ... je garde une liste visible de fichiers à télécharger et à partir de là, l'utilisateur peut supprimer ... il est évident que la suppression d'une balise <li> dans une balise <ul> est simple ... donc la seule méthode que j'ai proposée est de conserver une liste secondaire des fichiers supprimés et y faire référence pendant le processus de téléchargement ... donc si le fichier est dans la liste de téléchargement, je le saute simplement et l'utilisateur n'est pas plus sage.
cary abramoff

Lorsque vous affectez l' FileListobjet à la myReadWriteListvariable, il change son type de Arrayà FileList, donc ce n'est pas une solution.
adlr0

-2

Je change juste le type d'entrée au texte et reviens au fichier: D


Ceci est considéré comme un commentaire
Ivan Kaloyanov

Comment est censé fonctionner? Comment avez-vous réussi cela?
Ulrich Dohou
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.