The aim of this gist is to fix the following informative post about uploading via XHR.
The first part is the HTML. First of all, you really don't need JavaScript to upload a file but you do need a proper form. Secondly, inputs in a form shouild have a name, because the name is what the server will receive: not IDs, names!
Following the fixed form part.
<form
id="testForm"
action="//localhost:3000/upload"
method="post"
enctype="multipart/form-data"
>
<input type="file" name="file1"><br>
<input type="file" name="file2"><br>
<input type="file" name="file3"><br>
<input type="submit">
</form>
In case you go for the multiple files, here the full HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>form upload</title>
<meta name="viewport" content="width=device-width">
</head>
<body>
<form
id="testForm"
action="//localhost:3000/upload"
method="post"
enctype="multipart/form-data"
>
<input type="file" name="file" multiple><br>
<input type="submit">
</form>
<script src="app.js" async></script>
</body>
</html>
Quoting the author:
So first off - yes I’m using jQuery and naked XHR calls. That’s ok. You don’t have to use jQuery and that’s fine too. Don’t stress over it.
I'm sorry but I do stress over it. If you use jQuery even if it's just to grab a form it's OK.
But if you create global variables and you use jQuery to .get(0)
all the fields, I think you're doing it wrong and people should not learn bad practices so easily.
On top of that, if you use a progressive enhancement approach, you can automatically have all the right info for both JS and non-JS cases.
document
.querySelector('#testForm')
.addEventListener('submit', function processForm(e) {
e.preventDefault();
console.log('processForm');
var form = e.currentTarget;
var formData = new FormData();
Array.prototype.forEach.call(
form.querySelectorAll('input[type=file]'),
function (input, i) {
// use the input name, don't invent another one
if (input.value) formData.append(input.name, input.files[0]);
}
);
var request = new XMLHttpRequest();
// use the form info, don't couple your JS with an end-point
request.open(form.method, form.action);
// want to distinguish from non-JS submits?
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
request.send(formData);
request.onload = function(e) {
console.log('Request Status', request.status);
};
})
;
At this point, writing some better JS that takes care of multiple files and disable the button until the upload is finished, is straight forward.
Here last example:
document
.querySelector('#testForm')
.addEventListener('submit', function processForm(e) {
e.preventDefault();
console.log('processForm');
var form = e.currentTarget;
var multipleFiles = form.querySelector('input[type=file]');
// only if there is something to do ...
if (multipleFiles.files.length) {
var submit = form.querySelector('[type=submit]');
var request = new XMLHttpRequest();
var formData = Array.prototype.reduce.call(
multipleFiles.files,
function (formData, file, i) {
formData.append(multipleFiles.name + i, file);
return formData;
},
new FormData()
);
// avoid multiple repeated uploads
// (histeric clicks on slow connection)
submit.disabled = true;
// do the request using form info
request.open(form.method, form.action);
// want to distinguish from non-JS submits?
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
request.send(formData);
request.onload = function(e) {
// clean up the form eventually
console.log('Request Status', request.status);
// make this form usable again
submit.disabled = false;
// enable the submit on abort/error too
// to make the user able to retry
};
}
})
;
Progress bar can be added via request.onprogress
event, using the total and the current data info.
Please pay a bit more attention to details, if the purpose of your blog post is to inform and teach developers how to do things.
It takes really nothing to write better code.
Thank you
Nice and succinct writeup. It would be fantastic to take this further and cover multipart uploads, whereby breaking the upload(s) into chunks and how xhr handles this. Its a topic I haven't been able to find much on - obviously because it relies on the server side stack - but it could be explored using best practices in theory.