It’s a lot simpler than you might expect!
So, you need to get files from your user’s browser to your server. Not just one file though. A whole bunch. A batch even. And each file needs to be able to have its own meta data sent with it, i.e. Title, Caption, Copyright, Tags, etc. And asking your user to send them one at a time is not an option.
You also want to be able to keep that slick single page application aesthetic you’ve work so gosh darn hard at. So you need to be able to send your payload without forcing the user to reload the page.
The building blocks
Our shopping list
- 1 x Form
- 1 x File input
- 1 x Submit button
The [mutiple] attribute
The first thing we need to make sure to do is set the mutiple attribute to the file input. This will signal to the browser that the input field is allowed to select more than one file at a time.
Watching for changes and grabbing all them files
Next up, we need to watch the file input for any changes. When we detect a change we then cache the file pointers in an array to be used when the form is submitted.
Intercepting the form submit event
So far, so good. Now we are going to hook into the form’s submit event to trigger our uploads.
First we grab a reference to the form DOM element, here called “file-catcher”, and then we attach an event listener for the form’s “submit” event. Thereafter we prevent the form from actually submitting using the event’s preventDefault() method. Finally we iterate over the cached fileList array and send each file to the sendFile method (see below).
FormData and Ajax
Ok, we’ve cached the files after the user has selected them. We’ve intercepted, and prevented, the form submission and piped the files through to the sendFile method. All we have to do now is actually write the sendFile method to accept the files and send them off to the server.
This simple method does two things.
- Creates a formData instance and sets the file as a value on it
- Creates a new XMLHttpRequest (Ajax), opens a connection to the server, and sends the formData instance
And that’s it! We’ve sent multiple files, in parallel, to our server.
Serial chunk vs. Parallel streams
Now it should be noted that it isn’t necessary to unpack the files from the file input and send them off separately. You could skip the array iteration and multiple requests in favor of just sending the entire collection of files in a single request quite easily.
I, however, chose not to do that. One of the reasons was that our server was already setup to accept single file uploads (with metadata like Title, Caption, etc.) and to save our backend developer having the adapt the server code to accept multiple files, it made sense to update the UI codebase to send each file individually.
Another reason was to fold the upload time over on itself, by leveraging the browser’s ability to handle multiple server connections simultaneously. Thereby allowing the files to stream in parallel.
And lastly, fault tolerance. By splitting the files into separate requests, this strategy allows for a file upload to fail in isolation. In other words, if the connection fails for the request, or the file is invalidated by the server, or any other reason, that file upload will fail by itself and should not necessarily affect any of the other uploads.
This walkthrough of this strategy is intentionally simplistic in order to focus on the core mechanics and structures that make it work. It is in no way a fully fledged solution and would not be advisable to use it as is in a production environment.
What is missing here are things like:
- Form validation on submit to ensure that files have actually been selected
- Request response handling to notify the user when a file is successfully upload, or if it fails
- Initiate file uploads immediately after the user selects them. By doing this you could reduce the perceived upload time as the user spends time filling out any file related form fields.
The full example
Here’s a JSFiddle that showcases this strategy as a whole. When running it I would suggest you have your browser tools open on the network tab, and inspect the request payload. You will see the file data in there!