34

I have:

<input type="file" id="f" name="f" onchange="c()" multiple />

Every time the user selects a file(s), I build a list of all the selected files by pushing each element of f.files to an array:

var Files = [];
function c() {
  for (var i=0;i<this.files.length;i++) {
    Files.push(this.files[i]);
  };
}

At form submit, f.files contains only the item(s) from the last select action, so I will need to update f.files with the list of FileList items I have accumulated:

const upload=document.getElementById("f");
upload.files = files;

But the second line gives:

Uncaught TypeError: Failed to set the 'files' property on 'HTMLInputElement': The provided value is not of type 'FileList'.

It is not happy that I am assigning it an array. How can I construct a FileList object from the list of FileList elements I have earlier collected?

Side question: I thought Javascript uses dynamic types. Why is it complaining about the wrong type here?

Old Geezer
  • 12,512
  • 25
  • 98
  • 173
  • I can't find a reference, but my strong suspicion is that `File` objects must be part of an active `FileList` associated with an `` element or a drag-and-drop target. – Pointy Aug 29 '18 at 13:47
  • Here someone answered similar question like this: https://stackoverflow.com/questions/38449440/javascript-create-file-list-object-from-list-of-files – Rana Khurram Aug 29 '18 at 13:57

2 Answers2

63

You can't modify a Filelist, but you can create a new one using a DataTransfer object, and if you wish you can copy your data into it to create a duplicate with the specific change you want to make.

let list = new DataTransfer();
let file = new File(["content"], "filename.jpg");
list.items.add(file);

let myFileList = list.files;

You can then set it as the file attribute of the DOM node:

fileInput.files = myFileList;

If you wished, you could iterate over your old FileList, copying files to the new one.

superluminary
  • 43,182
  • 23
  • 145
  • 146
41

It's like you said

Failed to set the 'files' property on 'HTMLInputElement': The provided value is not of type 'FileList'.

you can only set the files with a FileList instance, unfortunately the FileList is not constructible or changeable, but there is a way to get one in a round about way

/**
 * @params {File[]} files Array of files to add to the FileList
 * @return {FileList}
 */
function FileListItems (files) {
  var b = new ClipboardEvent("").clipboardData || new DataTransfer()
  for (var i = 0, len = files.length; i<len; i++) b.items.add(files[i])
  return b.files
}

var files = [
  new File(['content'], 'sample1.txt'),
  new File(['abc'], 'sample2.txt')
];


fileInput.files = new FileListItems(files)
console.log(fileInput.files)
<input type="file" id="fileInput" multiple />

doing this will trigger a change event, so you might want to toggle the change event listener on and off

Endless
  • 29,359
  • 11
  • 97
  • 120
  • 3
    As a side note, this does not work on iOS Safari. I've yet to solve. – Jarvis Jul 19 '19 at 23:10
  • 1
    This doesn't work on test, ex. node complain `ClipboardEvent` not defined. – windmaomao Apr 04 '21 at 14:07
  • @windmaomao then you are probably using a old browser that don't have the global ClipboardEvent what you want to do in this case is to first check if ClipboardEvent exist and if not use the DataTransfer instead. – Endless Apr 04 '21 at 18:00
  • According to https://developer.apple.com/forums/thread/133001, "user interaction is necessary for clipboard access". – Code4R7 Nov 01 '21 at 11:31
  • For me it worked using `fileInput.prop('files', new FileListItems(files))` instead of `fileInput.files = new FileListItems(files)` – Lipsyor Nov 15 '21 at 12:29
  • FileListItems is a method, not object. Right? – eastwater May 21 '22 at 09:25
  • FileListItems is a function that return a [FileList](https://developer.mozilla.org/en-US/docs/Web/API/FileList) class of Files – Endless May 21 '22 at 15:16