开发者

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开发者_Python百科 I do it?


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>


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.


What you want is using the multiple attribute on the input element. That way, in newer browsers user will be able to select multiple files to upload.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜