Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Uploading multiple files at once, and without jQuery

The aim of this gist is to fix the following informative post about uploading via XHR.

Fixing the HTML

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>

Fixing the JavaScript

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);
    };
  })
;

Improving JS

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.

As Summary

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

@vonKristoff

This comment has been minimized.

Copy link

vonKristoff commented Nov 10, 2017

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.

@sallamTanna

This comment has been minimized.

Copy link

sallamTanna commented Jun 27, 2018

Why I got "document is not defined" error ??

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.