How To Set The Value Of A File Input
It’s always been impossible to set the value of a file input element with JavaScript, but this has changed last year and it’s now broadly supported, let’s see how it’s done.
If you’re interested in why this is useful, read on below, else jump to the solution
Why Is Setting The Value Of A File Input Useful?
Being able to set the value of a file input comes in handy when we’ve edited an image in the browser, or when one or more files have been dropped on the page and we want to upload them to a server.
In both situations we end up with File objects that we can’t leave anywhere but in memory. We can serve the files as downloads, upload them asynchronously, or convert them to base64 strings and store them in a hidden text field, but we couldn’t store them in a file input element.
When we’re in control of the backend we can create an API to handle asynchronous file uploads or convert base64 encoded files back to file ojects. But there are a lot of situations where we don’t control the backend.
- We’re using a NoCode platform or service.
- We built a web component and don’t know where it’s going to be used.
- There’s simply no time or people available to make the required backend changes.
- The backend is so old that literally no one wants to touch it.
In all these situations there’s no way to upload File objects created in the browser.
The moment we can set the value of a file input the backend seizes to be part of the equation, meaning we can finally progressively enhance the file input element.
Updating The File Input Files Property
We’ll set up a file input element and a short script that sets the file input files
property.
The script creates a new File
object and stores it in myFile
We then use DataTransfer
to wrap the file in a FileList
which we can assign to the files
property of our file input element.
<input type="file" />
<script>
// Get a reference to our file input
const fileInput = document.querySelector('input[type="file"]');
// Create a new File object
const myFile = new File(['Hello World!'], 'myFile.txt', {
type: 'text/plain',
lastModified: new Date(),
});
// Now let's create a DataTransfer to get a FileList
const dataTransfer = new DataTransfer();
dataTransfer.items.add(myFile);
fileInput.files = dataTransfer.files;
</script>
And presto! The file input now contains “myFile.txt”
This works on all modern browsers. 🎉
Don’t see any file info? You’re probably browsing the web on Safari for MacOS 👇
But Safari
Safari was the last browser to add support for the DataTransfer
constructor, it was added in version 14.1.
While Safari for MacOS detects the field has a value assigned it doesn’t show the file name. For some situations that’s fine, for others that is bad UX.
Interestingly it does work correctly on Safari for iOS 🤷♂️
Let’s give Safari for MacOS a little help using this knowledge:
- Webkit based browsers can render pseudo-elements inside a file input.
- The
content
of a pseudo-element can be set to an attribute value usingattr()
- Safari populates the
webkitEntries
property, Chrome doesn’t.
To target Safari we’ll check if webkitEntries
has a length. Then we’ll set the current file name to the data-file
attribute, finally we’ll add a pseudo-element and set its content
property value to the value of the data-file
attribute.
<style>
/* Add pseudo-element only if data attribute is set */
input[data-file]::after {
content: attr(data-file);
margin-left: 0.375em;
}
</style>
<input type="file" />
<script>
// Get a reference to our file input
const fileInput = document.querySelector('input[type="file"]');
// Create a new File object
const myFile = new File(['Hello World!'], 'myFile.txt', {
type: 'text/plain',
lastModified: new Date(),
});
// Now let's create a FileList
const dataTransfer = new DataTransfer();
dataTransfer.items.add(myFile);
fileInput.files = dataTransfer.files;
// Help Safari out
if (fileInput.webkitEntries.length) {
fileInput.dataset.file = `${dataTransfer.files[0].name}`;
}
</script>
The field below now shows the file name as well.
In a real project we’d probably want a more robust solution.
We could for example dispatch a custom 'change'
event and then update the data-file
attribute according to the contents of the files
property.
Additionally we’d have to deal with multiple files, situations where the field is cleared, or where the user inputs a new file. But that’s for another time.
For now let’s just celebrate the fact that we can finally set the value of a file input element. 😅
Conclusion
We worked around the limitation of FileList
not having a constructor by creating a DataTransfer
instance, populating it with files, and then getting the files
property from it.
To finish things of we helped Safari to correctly show the currently selected file name.
That’s it. It took a while but we finally have a viable solution to set the value of a file input element. I’ve been using this solution with Pintura in production since last year, and it’s been working wonderfully.