Skip to content

Instantly share code, notes, and snippets.

@WebReflection
Last active June 30, 2021 14:12
  • Star 21 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save WebReflection/f04425ce4cfeb18d75236cb50255e4bc to your computer and use it in GitHub Desktop.
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

@sallamTanna
Copy link

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

@samxtu
Copy link

samxtu commented Feb 25, 2020

Thanks for the article:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment