Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change a file input's FileList programmatically?

I have this input of type "file", and I want to change its files list. Example:

<input type = "file" id = "fileinput" />
<script type = "text/javascript">
  document.getElementById("fileinput").files = [10];
</script>

The problem is that the fileinput element's files list is not set. How do I do it?

like image 397
dave Avatar asked Apr 12 '11 08:04

dave


People also ask

How do I reset the input on a file?

To reset a file input in React, set the input's value to null in your handleChange function, e.g. event. target. value = null . Setting the element's value property to null resets the file input.

What is FileList used for?

FileList is a command-line utility that generates a CSV file listing the contents of a given directory. By default, the list includes the file name, size, and path, as well as the last-access, last-modified, and creation dates, etc. You can easily import all results to a spreadsheet or database.

What does FileList mean?

Summary. FileList is an object which represents an array of individually selected files from the underlying system.


2 Answers

It's indeed impossible to add a local file to a file input that the user didn't request; however, there is a way to add Blob or File objects to or remove specific files from a file input.


To change the files in the file input, you have to replace the fileInput.files with another FileList object.

The problem is that FileList objects are immutable and have no constructor exposed to JS.

The only way of creating custom FileLists is by abusing DataTransfer, an API designed for transferring files using drag-and-drop or through the clipboard.

It is supported in all mainstream browsers, but not in IE, and only since version 14.1 in Safari. You can check it here.

You can create a DataTransfer object by invoking its constructor without arguments:

const dataTransfer = new DataTransfer()

The created DataTransfer object has a files property, which is, fortunately, a FileList containing all files the DataTransfer has.

But then how to add files to the DataTransfer?

Each DataTransfer has a DataTransferItemList associated with it (accessible through its items property) that has the methods for adding (and removing) items to (and from) the DataTransfer.

To add a file, you have to call the add method on it and pass a File object.

Like so:

dataTransfer.items.add(file)

If you have data that is not in a File object (e.g. a Blob or ArrayBuffer of bytes), you can use the File constructor, passing the data, the filename, and optionally an object with type and lastModified properties to it:

const file = new File([blob], 'file.txt', {type: 'text/plain', lastModified: modificationDate})

So, to sum up, we have something like this:

new DataTransfer()
 |
 v
DataTransfer 
 |  |
 |  +--[ .items ]---> DataTransferItemList
 |                      |
 +--[ .files ]------+   +---[ .add(file) ]---> DataTransferItem
                    |               ^
                    v               |
 +--[ .files ] <-- FileList         |
 |                                  +--- File <--- new File([data], filename, options)
 |                                                            ^
 |                                                            |
<input type="file">      Blob ----------+---------------------+
                         ArrayBuffer ---+
                         string --------+

I'm sure this is quite messy right now, but let's see some code!

If you wanted to create a file named hello.txt containing Hello world! and set the input to contain this file, here's how can you do it:

const fileInput = document.getElementById('fileInput')

const dataTransfer = new DataTransfer()

const file = new File(['Hello world!'], 'hello.txt', {type: 'text/plain'})

dataTransfer.items.add(file)

fileInput.files = dataTransfer.files
<p>Notice that the file input contains "hello.txt" now: </p>
<input type="file" id="fileInput" />

But instead of replacing the files, how can you edit the list of files already in the input?

  1. Create a DataTransfer
  2. Add all existing files from the input except the ones you want to remove
  3. Add the files you want to add
  4. Replace the files in the input with the ones in the DataTransfer

See also this answer of mine about that topic.

For doing this, I've created a pair of functions for getting and setting the file list from an array of Files, so the transformations can be performed on arrays instead of doing complicated stuff with DataTransfers:

function getFiles(input){
  const files = new Array(input.files.length)
  for(let i = 0; i < input.files.length; i++)
    files[i] = input.files.item(i)
  return files
}

function setFiles(input, files){
  const dataTransfer = new DataTransfer()
  for(const file of files)
    dataTransfer.items.add(file)
  input.files = dataTransfer.files
}

You can use them like this:

function getFiles(input){
  const files = new Array(input.files.length)
  for(let i = 0; i < input.files.length; i++)
    files[i] = input.files.item(i)
  return files
}

function setFiles(input, files){
  const dataTransfer = new DataTransfer()
  for(const file of files)
    dataTransfer.items.add(file)
  input.files = dataTransfer.files
}

const fileInput = document.querySelector('#fileInput')

document.querySelector('#removeFirst').addEventListener('click', () => {
  const files = getFiles(fileInput)
  
  files.shift()
  
  setFiles(fileInput, files)
})
document.querySelector('#removeLastModified').addEventListener('click', () => {
  const files = getFiles(fileInput)
  
  let latest = 0, latestIndex
  for(let i = 0; i < files.length; i++)
    if(files[i].lastModified > latest){
      latest = files[i].lastModified
      latestIndex = i
    }
  files.splice(latestIndex, 1)  
  
  setFiles(fileInput, files)
})
document.querySelector('#addFile').addEventListener('click', () => {
  const files = getFiles(fileInput)
  
  const newFiles = getFiles(document.querySelector('#addFileInput'))
  files.push(...newFiles)
  
  setFiles(fileInput, files)
})
document.querySelector('#addRandomHello').addEventListener('click', () => {
  const files = getFiles(fileInput)
  
  const newFile = new File(['Hello world!'], 'hello.txt', {type: 'text/plain'})
  const index = Math.floor(Math.random() * (files.length + 1))
  files.splice(index, 0, newFile)
  
  setFiles(fileInput, files)
})
Hint: hover over the file input to see the list of all files in it <br>
<input type="file" id="fileInput" multiple ><br><br>
<button id="removeFirst">Remove first file</button><br>
<button id="removeLastModified">Remove latest modified file</button><br>
<button id="addFile">Append file from this input</button> <input type="file" id="addFileInput" /><br>
<button id="addRandomHello">Add a hello.txt file to a random place</button><br>
like image 109
FZs Avatar answered Oct 13 '22 00:10

FZs


For security reasons, browsers prevent javascript from changing the files which will be uploaded: only the user can select files via the user interface. This is to prevent an evil script to upload /etc/passwd, for example, without the user knowing.

The one exception is that calling "reset" on the form will clear the file or filelist, but you can never add to programmatically.

like image 25
odrm Avatar answered Oct 12 '22 22:10

odrm