30

I have this DOM:

var id = 0;

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

That get's the first file on the 0th index. File properties are listed and all works, but...

How do we remove items from the DOM's [object FileList] with JavaScript?

  • 2
    Possible duplicate of [How to remove one specific selected file from input file control](https://stackoverflow.com/questions/19060378/how-to-remove-one-specific-selected-file-from-input-file-control) – Endless Dec 09 '17 at 12:40
  • if you are using `React` to achive this then `const fileRef = useRef(null);
    fileRef.current.value = null;
    – Azhar Uddin Sheikh Feb 23 '23 at 07:17

9 Answers9

37

Finally found a way! I knew before that input.files would accept a FileList but the only way to get it was through a drag and drop event.

But now i know how to construct a own FileList!

This works in chrome (and maybe some other)

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
}
<input type="file" multiple id="input">

This works in 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
}
<input type="file" multiple id="input">

The thing is you need to loop over each file in the input, add those you still want to keep and assign the file.files with the new list of files.

Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
Endless
  • 34,080
  • 13
  • 108
  • 131
  • This is very helpful. Been scouring SO for some 5 hours. Phew! Lots of questions there looking for this. Hope I have time to refer this answer. Thanks a lot @Endless. – Ricardo Green Jan 16 '19 at 09:52
  • This is a true gem of an answer. I've tried and failed to create an elusive `FileList` in a number of different ways, but using a drag/drop DataTransfer object is brilliant – jpschroeder Nov 17 '19 at 04:09
  • If you have access to the files via a FormData object, you can just add the FormData object's file values to the DataTransfer object instead of declaring a new File with each addition. – default123 Sep 14 '20 at 03:13
  • Unfortunately, datatransfer is still not supported in safari. Had implemented it completely but have to drop it due to safari. – Jatin Bansal Oct 03 '20 at 09:51
21

I' am afraid that you cannot delete objects from FileList object directly. Just assign $('input:file#upload')[0].files to an Array and then remove items from that array using splice or method of your choice and then use that Array.

Imran Latif
  • 985
  • 8
  • 21
  • 7
    well I need to remove them from the Upload Queue, not from a copy of that list... is this possible ? –  Jun 05 '13 at 16:23
  • 1
    You cannot remove files from `FileList` but as I said that all you want is to assign those `file objects` from `FileList` into an `array` and then you can do anything with them. Now this solution depends upon whether you want to upload files via `Ajax or via `Form Submit`. If you suppose to upload files via `Ajax` then the `Array` solution will work perfectly as you have same `file objects` with exact same properties. – Imran Latif Jun 05 '13 at 17:29
  • Hm... yes, you are right... I might just read the files with `FileReader` :) thanks on these tips mate! –  Jun 05 '13 at 17:50
  • @ZlatanO. My pleasure :-). So it seems that you don't actually want to upload files but interested in doing some useful stuff at Client-Side by reading their contents :-). – Imran Latif Jun 05 '13 at 17:53
  • No No! I want to upload files. I was using this tutorial: http://net.tutsplus.com/tutorials/javascript-ajax/uploading-files-with-ajax/ - And this is my first encounter when dealing and manipulating the `input:file element` and using the `FileReader` class. Thanks nontheless! :) Cheers! –  Jun 06 '13 at 06:46
  • I' am really happy that my answer helps you :-). Thanks. – Imran Latif Jun 06 '13 at 06:58
  • 1
    Your suggestion to "just assign `$('input:file#upload')[0].files` to an `Array`" does not work, as it's read-only. Did you even try it? – Spooky Aug 31 '15 at 14:50
  • 1
    @Spooky: Thanks for commenting, Yeah, off-course I tried my solution. I think you didn't read the question and problem statement carefully. You cannot remove files from `files` array that is associated with file-control but you can assign that array to a standard array in JavaScript and then you can remove files from that standard array which off-course have no impact on `files` array of file-control but you can use that standard array to say upload remaining files to server. – Imran Latif Aug 31 '15 at 19:07
  • @ImranLatif please, what do you mean by "then use that Array"? how can I use it instead of the original file input when I submit the form? – Omar Feb 09 '21 at 10:21
  • @Omar I meant use the array instead to upload files instead of using `files` array. – Imran Latif Feb 09 '21 at 11:15
15

I have found very quick & short workaround for this. Tested in many popular browsers (Chrome, Firefox, Safari);

First, you have to convert FileList to an Array

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

to delete the particular element use this

newFileList.splice(index,1);
MeVimalkumar
  • 3,192
  • 2
  • 15
  • 26
8

Since you can't directly change the fileList object, you can instantiate a new DataTransfer object and iteratively copy the desired attached files to it. Then you assign it to the input's files property.

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: I tried using the DataTransferObject.items.remove(index) method but for some reason it clears the whole fileList array. That's why I used a for loop.

Fadhili Njagi
  • 108
  • 1
  • 4
5

The most practical way to remove FileList object is to just remove the file input itself from the DOM and re-append it again. This will remove all the items from the file list.

I know a lot of people will say this is not an elegant solution, but it very easy to implement, a better approach for most cases, and you can do what is important with the input file, validation!

By now you see that to control the FileList object is hard. If you really need to manipulate an individual file item, read Multi-File Uploads and Multiple Selects (Part 2), by RAYMOND CAMDEN . I prefer to just make the user select the files again (if he done goofy) and give him an error message of what went wrong. This will not make the user experience bad.

As a reminder, be aware that input file brings security weakness (Vulnerability: Unrestricted File Upload).

Since this post didn't really answer the question, I know it won't get any points, but really consider the alternatives. For my case when I was implementing deleting a file object item, it didn't make sense continuing the upload after some file didn't pass the validation, even if some files were ok. In the end, the user would still have to open the input file and redo the process. So, for my case, this feature was just adding complexity, and it was not in the specification this much control for an input file.

Bellow an example with validation that deletes all the FileList object when fails:

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>

I hope this helps in some way.

Volfegan
  • 239
  • 3
  • 5
1

No, We can make it removable. I implemented this and it works definitely.

First you need to initialize this variables

var newImageObj = [];
var ImageNo = 0;

Then write this code on file input's change

$("#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.");
            }
        });

Then at last this 2 functions will help to do the rest

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

At last when you submit your form than take the files from the array

Rush.2707
  • 685
  • 10
  • 29
0

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

Removing a file from fileList before sending by ajax.

1.create filesToUpload list var filesToUpload = [];

  1. Add FileId and File itself to above Array.(we will need FileId to find deleted file by compare them with it )
  2. when ckick on remove icon will compare deleted file with filesToUpload and remove it .
  3. When you send by ajax , the ajax will take values in filesToUpload list and send it.

Code

//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);
            }
        });
    });
})()
Mohammad S
  • 81
  • 1
  • 3
0

A Pure Vanilla JS Solution that doesn't use FileReader API or DataTransfer Methods.

This is a modified version of a jQuery fiddle

How it works??

  1. Uses an array to populate file objects from html input selection.
  2. Resets html input to null, after every file selection. (But array values remain)
  3. If we want remove a file, we're slicing that element from array.
  4. And when finally if the user wish to upload the files, we're creating a new formData with values from array and upload via fetch api

Demo:

var filesToUpload = []; // Array to store files
const fileInput = document.querySelector('input[type="file"]');

fileInput.addEventListener('change', (e) => {

  for (let i = 0; i < e.target.files.length; i++) {
    let myFile = e.target.files[i];
    let myFileID = "FID" + (1000 + Math.random() * 9000).toFixed(0);

    filesToUpload.push({
      file: myFile,
      FID: myFileID
    });
  };
  display();
  //reset the input to null - nice little chrome bug!
  e.target.value = null;
});

const removeFile = (x) => {
  for (let i = 0; i < filesToUpload.length; i++) {
    if (filesToUpload[i].FID === x)
      filesToUpload.splice(i, 1);
  }
  display();
}

const display = () => {
  document.getElementById("myFiles").innerHTML = "";
  for (let i = 0; i < filesToUpload.length; i++) {
    document.getElementById("myFiles").innerHTML += `<li>${filesToUpload[i].file.name}<button onclick="removeFile('${filesToUpload[i].FID}')">Remove</button></li>`;
  }
}

const initUpload = () => {
  console.log(filesToUpload);

  let formData = new FormData();

  for (let i = 0; i < filesToUpload.length; i++) {
    formData.append("files", filesToUpload[i].file);
  }
  /*
    fetch("https://server_upload_route/", {
            method: 'POST',
            body: formData
        }).then(response => response.json())
        .then(data => {
            console.log(data);
        })
        .catch(error => {
            alert("POST Failed");
            console.log(error);
        });
    */
}
fieldset {
  max-width: 500px;
}

#uploadBtn {
  float: right
}
<fieldset>
  <legend>Multiple File upload</legend>
  <input type="file" hidden multiple />
  <br />
  <ol id="myFiles"></ol>
  <br />
  <button onclick="fileInput.click()">Browse</button>
  <button id="uploadBtn" onclick="initUpload()">Start upload</button>
</fieldset>

It is also possible to rename the file in FormData, So that the files get uploaded in a new name to the server (by using a third filename parameter in Formdata.append)

A5H1Q
  • 544
  • 7
  • 15