从多个“输入:文件”中删除 FileList 项

Posted

技术标签:

【中文标题】从多个“输入:文件”中删除 FileList 项【英文标题】:Remove a FileList item from a multiple "input:file" 【发布时间】:2013-06-01 08:53:13 【问题描述】:

我有这个 DOM:

var id = 0;

$('input:file#upload')[0].files[ id ]

这是第 0 个索引上的第一个文件。列出了文件属性并且一切正常,但是...

我们如何使用 javascript 从 DOM 的 [object FileList] 中删除项目?

【问题讨论】:

How to remove one specific selected file from input file control的可能重复 【参考方案1】:

恐怕您不能直接从FileList object 中删除对象。只需将$('input:file#upload')[0].files 分配给Array,然后使用splice 或您选择的方法从该数组中删除项目,然后使用Array

【讨论】:

我需要将它们从上传队列中删除,而不是从该列表的副本中删除...这可能吗? 您无法从FileList 中删除文件,但正如我所说,您只需将FileList 中的file objects 分配到array 中,然后您就可以对它们进行任何操作。现在此解决方案取决于您是否要通过Ajax or via Form Submit. If you suppose to upload files via Ajax` 上传文件,然后Array 解决方案将完美运行,因为您拥有相同的file objects 具有完全相同的属性。 嗯...是的,你是对的...我可能只是用FileReader 阅读文件 :) 感谢这些提示,伙计! 您关于“将$('input:file#upload')[0].files 分配给Array”的建议不起作用,因为它是只读的。你试过了吗? @Spooky:感谢您的评论,是的,我尝试了我的解决方案。我认为您没有仔细阅读问题和问题陈述。您无法从与文件控制关联的files 数组中删除文件,但您可以将该数组分配给 JavaScript 中的标准数组,然后您可以从该标准数组中删除偏离路线对files 数组没有影响的文件文件控制,但您可以使用该标准数组将剩余文件上传到服务器。【参考方案2】:

不,我们可以将其拆下。 我实现了这一点,它确实有效。

首先你需要初始化这个变量

var newImageObj = [];
var ImageNo = 0;

然后在文件输入的变化上写这段代码

$("#exampleInputFileProduct").change(function () 

            var fileUpload = document.getElementById("exampleInputFileProduct");

            //$("#mainImages").html('');
            //$("#subImages").html('');

            if (typeof (FileReader) != "undefined") 

                //Here Check File Extension
                var regex = /^([a-zA-Z0-9\s_\\.\-:])+(.jpg|.jpeg|.gif|.png)$/;


                for (var i = 0; i < fileUpload.files.length; i++) 
                    var j = 0;
                    var file = fileUpload.files[i];
                    var NewFile = fileUpload.files[i];
                    //Here Check File Size 1MB = 1000000 Bytes
                    if (file.size < 2048000) 
                        if (regex.test(file.name.toLowerCase())) 
                            var reader = new FileReader();
                            reader.onload = function (e) 

                                if ($("#mainImages").find(".item").attr("id") == "FirstSlider") 
                                    $("#mainImages").html('');
                                    $("#subImages").html('');
                                    $("#subImages").append("<div class='item active'></div>");
                                

                                if ($("#mainImages").find(".item").hasClass("active")) 
                                    $("#mainImages").append("<div class='item " + ImageNo + "_CClass\'><i class='fa fa-times customIcon' onclick='RemoveImage(\"" + ImageNo + "_CClass\",\"" + fileUpload.files[j].name.toLowerCase() + "\")'></i><img class='CImage' src='" + e.target.result + "' alt='' /></div>");
                                 else 
                                    $("#mainImages").append("<div class='item active " + ImageNo + "_CClass'><i class='fa fa-times customIcon' onclick='RemoveImage(\"" + ImageNo + "_CClass\",\"" + fileUpload.files[j].name.toLowerCase() + "\")'></i><img class='CImage' src='" + e.target.result + "' alt='' /></div>");
                                

                                //if ($("#subImages").find(".item").length == 0) 
                                //    $("#subImages").append("<div class='item active'></div>");
                                // else 
                                if (($("#subImages").find(".item").find("div").length / 5) >= $("#subImages").find(".item").length) 
                                    $("#subImages").append("<div class='item'></div>");
                                
                                //

                                var append = 0;

                                $.each($("#subImages").find(".item"), function (p, pelement) 
                                    if (append == 0) 
                                        if ($(pelement).find("div").length != 5) 
                                            var newID = $(pelement).find("div").length;
                                            newID = newID;
                                            $(pelement).append("<div onclick='LoadImage(\"" + ImageNo + "_CClass\")' data-slide-to='" + newID + "' class='thumb " + ImageNo + "_CClass'> <img src='" + e.target.result + "' alt=''></div>");
                                            append = append + 1;
                                        
                                    
                                )

                                j = j + 1;

                                ImageNo = ImageNo + 1;
                            

                            newImageObj.push(file);

                            reader.readAsDataURL(file);
                        
                    
                
             else 
                alert("This browser does not support HTML5 FileReader.");
            
        );

那么最后这两个函数将帮助完成剩下的工作

function LoadImage(objclass) 
            $("#mainImages").find(".item").removeClass("active");
            $("#mainImages").find("." + objclass + "").addClass("active");
        

        function RemoveImage(objclass, ImageName) 

            $.each(newImageObj, function (e, element) 
                if ($(this)[0].name.toLowerCase().trim() == ImageName.trim()) 
                    newImageObj.pop(this);
                
            );

            $("#mainImages").find("." + objclass + "").remove();
            $("#subImages").find(".item").find("." + objclass + "").remove();

            if ($("#mainImages").find(".item").length == 0) 
                $("#mainImages").append("<div class='item active'><i class='fa fa-times customIcon'></i><img class='CImage' src='/Content/img/DefaultProduct.gif' alt='' /></div>");
                $("#subImages").append("<div class='item active'><div data-target='#carousel' data-slide-to='0' class='thumb'> <img src='/Content/img/DefaultProduct.gif' alt=''></div></div></div>");
             else 
                $("#mainImages").find(".item").removeClass("active");
                $("#mainImages").find(".item:first-child").addClass("active");
                $("#subImages").find(".item").removeClass("active");
                $("#subImages").find(".item:first-child").addClass("active");
            
        

最后,当您提交表单而不是从数组中获取文件时

【讨论】:

【参考方案3】:

我为此找到了非常快速且简短的解决方法。在许多流行的浏览器(Chrome、Firefox、Safari)中测试;

首先,您必须将 FileList 转换为数组

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

要删除特定元素,请使用 this

newFileList.splice(index,1);

【讨论】:

索引从何而来? 要从该数组中删除的文件的索引 我一直在努力解决如何删除文件数组的索引。您的建议对我很有帮助,谢谢【参考方案4】:

终于找到方法了! 我之前知道input.files 会接受 FileList,但获得它的唯一方法是通过拖放事件。

但现在我知道如何构建自己的 FileList!

这适用于 chrome(可能还有其他)

const dt = new DataTransfer()
dt.items.add(new File([], 'a.txt'))
input.files = dt.files

// This will remove the first item when selecting many files
input.onchange = () => 
  const dt = new DataTransfer()

  for (let file of input.files)
    if (file !== input.files[0]) 
      dt.items.add(file)

  input.onchange = null // remove event listener
  input.files = dt.files // this will trigger a change event
&lt;input type="file" multiple id="input"&gt;

这适用于 Firefox

const cd = new ClipboardEvent("").clipboardData
cd.items.add(new File(['a'], 'a.txt'))
input.files = cd.files

// This will remove the fist item when selecting many files
input.onchange = () => 
  const dt = new DataTransfer()

  for (let file of input.files)
    if (file !== input.files[0]) 
      dt.items.add(file)

  input.onchange = null // remove event listener
  input.files = dt.files // this will trigger a change event
&lt;input type="file" multiple id="input"&gt;

问题是您需要遍历输入中的每个文件,添加您仍想保留的文件,并为 file.files 分配新的文件列表。

【讨论】:

这很有帮助。已经冲刷了大约 5 个小时。呸!很多问题都在寻找这个。希望我有时间参考这个答案。非常感谢@Endless。 这是一个真正的答案。我尝试过以多种不同的方式创建难以捉摸的FileList,但都失败了,但是使用拖放 DataTransfer 对象非常棒? 如果您可以通过 FormData 对象访问文件,则只需将 FormData 对象的文件值添加到 DataTransfer 对象,而不是在每次添加时声明一个新文件。 不幸的是,safari 仍然不支持数据传输。已经完全实现了它,但由于 safari 不得不放弃它。【参考方案5】:

删除FileList object 最实用的方法是从 DOM 中删除文件输入本身并重新附加它。这将从文件列表中删除所有项目。

我知道很多人会说这不是一个优雅的解决方案,但它很容易实现,在大多数情况下是一种更好的方法,并且您可以对输入文件进行重要的验证!

现在你看到控制FileList object 很难。如果您确实需要操作单个文件项,请阅读Multi-File Uploads and Multiple Selects (Part 2), by RAYMOND CAMDEN 。我更喜欢让用户再次选择文件(如果他做错了)并给他一个错误消息,说明出了什么问题。这不会使用户体验变差。

提醒一下,请注意输入文件会带来安全漏洞 (Vulnerability: Unrestricted File Upload)。

由于这篇文章并没有真正回答这个问题,我知道它不会得到任何分数,但真正考虑替代方案。对于我在实施删除文件对象项时的情况,即使某些文件没问题,在某些文件未通过验证后继续上传也没有意义。最后,用户仍然必须打开输入文件并重做该过程。因此,就我而言,此功能只是增加了复杂性,规范中并没有对输入文件进行如此多的控制。

下面是一个验证示例,当失败时删除所有FileList object

function validateFormfile(inputTypeFile_id) 
  $(inputTypeFile_id).change((event) => 
    //check if files were select, if not, nothing is done
    if (event.target.files.length > 0) 
      let fileName;
      let totalsize = 0;
      let notvalidate = false;
      for (let i = 0; i < event.target.files.length; i++) 

        fileName = event.target.files[i].name;
        fileSize = event.target.files[i].size;
        if (fileName != undefined || fileName != "") 

          if (validate_fileExtension(fileName) === false) 
            notvalidate = true;
            let errorMessage = "File upload must be of 'audio', 'image', 'video', 'text', or 'pdf' format!";
            //write you error function to show error to user
            //alertpanel(errorMessage);
            console.log(errorMessage);
            break;
          
          totalsize += Number(event.target.files[i].size);
          console.log(fileName, fileSize, "bytes");
        
      

      //check if file size is bigger than maxsize
      let maxsize = 10 * 1024 * 1024; //10Mb
      if (totalsize > maxsize && notvalidate === false) 
        notvalidate = true;
        let errorMessage = `Upload files cannot exceed the maximum of $maxsize bytes.`;
        //write you error function to show error to user
        //alertpanel(errorMessage);
        console.log(errorMessage);
      

      if (notvalidate) 
        //select the node where to append the input file
        let inputlabel = $(inputTypeFile_id).siblings().first();
      
        //we delete the input file element to delete its FileList object content and re-append to the DOM
        $(inputTypeFile_id).remove();
        let input_file = $('<input type="file" id="upload" name="upload" accept="application/pdf, text/plain, audio/*, video/*, image/*" multiple required>');

        //append the input file after the selected inputlabel node 
        inputlabel.after(input_file);

        //re init any event listener for the re-appended element
        validateFormfile(inputTypeFile_id);
      
    
  );


function validate_fileExtension(fileName) 
  let image_extensions = new Array("bmp", "jpg", "jpeg", "jpe", "jfif", "png", "gif");
  let text_extensions = new Array("txt", "pdf");
  let video_extensions = new Array("avi", "mpeg", "mpg", "mp4", "mkv");
  let audio_extensions = new Array("mp3", "acc", "wav", "ogg");
  let allowed_extensions = image_extensions.concat(text_extensions, video_extensions, audio_extensions);
  // split function will split the fileName by dot(.), and pop function will pop the last element from the array which will give you the extension as well. If there will be no extension then it will return the fileName.
  let file_extension = fileName.split('.').pop().toLowerCase();

  for (let i = 0; i <= allowed_extensions.length; i++) 
    if (allowed_extensions[i] == file_extension) 
      return true; // valid file extension
    
  
  return false;

//init event listener to input file
$(document).ready(
    validateFormfile("#upload")
  );
label,
input 
  display: block;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<label for="upload">Choose File(s) (Max: 10Mb)</label>
<input type="file" id="upload" name="upload" accept="application/pdf, text/plain, audio/*, video/*, image/*" multiple required>
<br><small>text|pdf|audio|image|video</small>

我希望这在某种程度上有所帮助。

【讨论】:

【参考方案6】:

function validateFormfile(inputTypeFile_id) 
  $(inputTypeFile_id).change((event) => 
    //check if files were select, if not, nothing is done
    if (event.target.files.length > 0) 
      let fileName;
      let totalsize = 0;
      let notvalidate = false;
      for (let i = 0; i < event.target.files.length; i++) 

        fileName = event.target.files[i].name;
        fileSize = event.target.files[i].size;
        if (fileName != undefined || fileName != "") 

          if (validate_fileExtension(fileName) === false) 
            notvalidate = true;
            let errorMessage = "File upload must be of 'audio', 'image', 'video', 'text', or 'pdf' format!";
            //write you error function to show error to user
            //alertpanel(errorMessage);
            console.log(errorMessage);
            break;
          
          totalsize += Number(event.target.files[i].size);
          console.log(fileName, fileSize, "bytes");
        
      

      //check if file size is bigger than maxsize
      let maxsize = 10 * 1024 * 1024; //10Mb
      if (totalsize > maxsize && notvalidate === false) 
        notvalidate = true;
        let errorMessage = `Upload files cannot exceed the maximum of $maxsize bytes.`;
        //write you error function to show error to user
        //alertpanel(errorMessage);
        console.log(errorMessage);
      

      if (notvalidate) 
        //select the node where to append the input file
        let inputlabel = $(inputTypeFile_id).siblings().first();
      
        //we delete the input file element to delete its FileList object content and re-append to the DOM
        $(inputTypeFile_id).remove();
        let input_file = $('<input type="file" id="upload" name="upload" accept="application/pdf, text/plain, audio/*, video/*, image/*" multiple required>');

        //append the input file after the selected inputlabel node 
        inputlabel.after(input_file);

        //re init any event listener for the re-appended element
        validateFormfile(inputTypeFile_id);
      
    
  );


function validate_fileExtension(fileName) 
  let image_extensions = new Array("bmp", "jpg", "jpeg", "jpe", "jfif", "png", "gif");
  let text_extensions = new Array("txt", "pdf");
  let video_extensions = new Array("avi", "mpeg", "mpg", "mp4", "mkv");
  let audio_extensions = new Array("mp3", "acc", "wav", "ogg");
  let allowed_extensions = image_extensions.concat(text_extensions, video_extensions, audio_extensions);
  // split function will split the fileName by dot(.), and pop function will pop the last element from the array which will give you the extension as well. If there will be no extension then it will return the fileName.
  let file_extension = fileName.split('.').pop().toLowerCase();

  for (let i = 0; i <= allowed_extensions.length; i++) 
    if (allowed_extensions[i] == file_extension) 
      return true; // valid file extension
    
  
  return false;

//init event listener to input file
$(document).ready(
    validateFormfile("#upload")
  );
label,
input 
  display: block;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<label for="upload">Choose File(s) (Max: 10Mb)</label>
<input type="file" id="upload" name="upload" accept="application/pdf, text/plain, audio/*, video/*, image/*" multiple required>
<br><small>text|pdf|audio|image|video</small>

【讨论】:

【参考方案7】:

在通过 ajax 发送之前从 fileList 中删除一个文件。

1.创建文件上传列表 var filesToUpload = [];

    将 FileId 和 File 本身添加到上面的数组中。(我们需要 FileId 来 通过比较找到已删除的文件) 当点击删除图标时,将删除的文件与 filesToUpload 并删除它。 当您通过 ajax 发送时,ajax 将采用 filesToUpload 中的值 列出并发送。

代码

//Upload multi files and removable from files when user  removed  file  ...
(function () 

    var filesToUpload = [];
    var fileIdCounter = 0;
    var fileIdCounterOnload = 0;

    $('#up').change(function (evt) 

        for (var i = 0; i < evt.target.files.length; i++) 
            fileIdCounter++;

            var file = evt.target.files[i];
            var fileId = "file" + fileIdCounter;

            filesToUpload.push(
                id: fileId,
                file: file
            );

            var reader = new FileReader();

            reader.onload = function (evt) 
                fileIdCounterOnload++;

                var fileIdOnload = "file" + fileIdCounterOnload;
                $("<li class=\"pip\"  data-fileid=\"" + fileIdOnload + "\">" +
                    "<img class=\"imageThumb\" src=\"" + evt.target.result + "\" title=\" \"/>" +
                    "<i class=\"icon-remove-circle remove removeFile\" ></i> " +
                    "</li>").insertAfter(".list");

                $(".remove").click(function () 

                    var fileId = $(this).parent(".pip").data("fileid");

                    // loop through the files array and check if the name of that file matches FileName
                    // and get the index of the match
        for (var i = 0; i < filesToUpload.length; ++i) //here will start compare them 
                        if (filesToUpload[i].id === fileId) 
                            filesToUpload.splice(i, 1);// delete a file from list.
                        
                    
                    $(this).parent(".pip").remove();// remove file from view .
                );
            ;
            reader.readAsDataURL(file); // convert to base64 string
        ;

        //reset the input to null - nice little chrome bug!
        evt.target.value = null;
    );


    $(".btn").click(function (e) 
        e.preventDefault();

        var formData = new FormData();

        for (var i = 0, len = filesToUpload.length; i < len; i++) 
            formData.append("files", filesToUpload[i].file);
        
        $.ajax(
            url: "/home/index2",
            data: formData,
            processData: false,
            contentType: false,
            type: "POST",
            success: function (data) 
                alert("DONE");
                filesToUpload = [];
                fileIdCounter = 0;
                fileIdCounterOnload = 0;
            ,
            error: function (data) 
                alert("ERROR - " + data.responseText);
            
        );
    );
)()

【讨论】:

【参考方案8】:

由于无法直接更改fileList 对象,因此可以实例化一个新的DataTransfer 对象,并迭代地将所需的附件复制到其中。然后将其分配给输入的 files 属性。

function removeFile(index)
    var attachments = document.getElementById("attachment").files; // <-- reference your file input here
    var fileBuffer = new DataTransfer();

    // append the file list to an array iteratively
    for (let i = 0; i < attachments.length; i++) 
        // Exclude file in specified index
        if (index !== i)
            fileBuffer.items.add(attachments[i]);
    
    
    // Assign buffer to file input
    document.getElementById("attachment").files = fileBuffer.files; // <-- according to your file input reference

PS: 我尝试使用 DataTransferObject.items.remove(index) 方法,但由于某种原因它清除了整个 fileList 数组。这就是我使用 for 循环的原因。

【讨论】:

这个解决方案对我有用,谢谢。

以上是关于从多个“输入:文件”中删除 FileList 项的主要内容,如果未能解决你的问题,请参考以下文章

从 FileList Ajax 上传之前删除文件

如何在不使用 JCL 中的 XSUM 的情况下从输入文件中删除重复项并将重复项写入文件? [关闭]

分别从多个有序列表中删除重复项

如何在从多个表中获取多行的同时删除 sql JOIN 中的重复项

从表中的多个重复项中删除特定记录

从 MySQL 中具有不同列的表的多个连接结果中删除重复项