0

I created a button to upload a picture file to the backend using a behind the scenes JS form. I do have the functionality of clicking the picture button opening a form to upload pictures. I tried it with a cat.jpg found here. I am using flask on the backend which checks that it is an approved type and then uploads it to a static folder. I added a simple print statement to see that the method is called, which it is not.

It seems my issue is with the addEventListener submit function. It does not seem to get called. I am assuming that because I did not make a submit button with the form, that is why it is not being called. However, the input raises a computer naive, file uploading that has its own submit, so I feel like the submit function should be correctly called in this case. Can I get any pointers, I am pretty new to JS, and Ajax!

My JS Code:

document.getElementById('button-picture').addEventListener('click', function() {

    // Create a form to upload the picture behind the scenes
    var picture_form = document.createElement('form');
    picture_form.setAttribute('id', 'image_upload_form');
    picture_form.setAttribute('enctype', 'multipart/form-data');
    picture_form.setAttribute('method', 'POST');
    var input_tag = document.createElement('input');
    input_tag.setAttribute('type', 'file');
    input_tag.setAttribute('id', 'file_upload');
    input_tag.setAttribute('name', 'files');
    input_tag.setAttribute('multiple', true);
    picture_form.append(input_tag);

    // Upload the picture to the backend when it is submitted.
    picture_form.addEventListener("submit", function(e) {
      // Do I need this?
      e.preventDefault();
      var request = new XMLHttpRequest();
      request.open('POST', '/upload-images', true);
      request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
      request.setRequestHeader('ProcessData', false)
      var csrf_token = "{{ csrf_token() }}";
      request.setRequestHeader('X-CSRFToken', csrf_token);
      var form_data = new FormData(picture_form.input.files);
      request.send(form_data);
    });
    input_tag.click()


    // Add markup Code to display it.

}, false);

My backend code looks what is provided in this SO question. Also, updated code to reflect this SO question to no avail.


update**, changed code:


    // Create a form to upload the picture behind the scenes
    var picture_form = document.createElement('form');
    picture_form.setAttribute('id', 'image_upload_form');
    picture_form.setAttribute('enctype', 'multipart/form-data');
    picture_form.setAttribute('method', 'POST');
    var input_tag = document.createElement('input');
    input_tag.setAttribute('type', 'file');
    input_tag.setAttribute('id', 'file_upload');
    input_tag.setAttribute('name', 'files');
    input_tag.setAttribute('multiple', true);
    picture_form.append(input_tag);
    var submit_event = new Event('submit');
    picture_form.dispatchEvent(submit_event);

    // Upload the picture to the backend when it is submitted.
    picture_form.addEventListener("submit", function(e) {
      alert("Hello");
      // Do I need this?
      // e.preventDefault();
      var request = new XMLHttpRequest();
      request.open('POST', '/upload_images', true);
      var csrf_token = "{{ csrf_token() }}";
      request.setRequestHeader('X-CSRFToken', csrf_token);
      var form_data = new FormData(picture_form);
      request.send(form_data);
    });
    input_tag.click()

Edit 2: picture of what happens when I pick the picture button.

David Frick
  • 641
  • 1
  • 9
  • 25
  • What is the motivation to use a hidden form to send data to the backend? – Emiel Zuurbier Feb 27 '20 at 22:48
  • It is for a article type editor of inserting a picture by pressing a picture button from within the editor and not having to reload the page or upload the picture separately. – David Frick Feb 27 '20 at 23:05

1 Answers1

1

To manually submit a form you would use the HTMLFormElement.submit() method. But this will not cause the form element to fire the submit event.

In this case you'll need to create an Event object and dispatch it on your form. This goes as follows.

var picture_form = document.createElement('form');
...
var submitEvent = new Event('submit');
picture_form.dispatchEvent(submitEvent);

This will trigger the submit event and will cause the callback on your event listener to be called.

But your code will break at the following line:

var form_data = new FormData(picture_form.input.files);

The FormData constructor takes in a <form> element as argument. Not a single input, as it is a collection of key-value pairs extracted from the <form> element. Change the line to the following:

var form_data = new FormData(picture_form);

Also the Content-Type header you include should be removed. The FormData object will automatically set the correct headers when being send with XMLHTTPRequest.

Edit Since you are only using the file select prompt to let the users select their images, you'll need to listen for a different event. Specifically the change event.

picture_form.addEventListener('change', function() {
  ...
});

This event will fire whenever the value of the input has been changed. In the case of the <input type="file"> element, that occurs when the user has selected the files.

No need for the new Event('submit') and dispatchEvent methods here for the change event will be called by the user and don't have to be manually dispatched.

Emiel Zuurbier
  • 19,095
  • 3
  • 17
  • 32
  • Okay, gotcha. I did have the FormData sending the form (it did seem off with the files being sent). However even with these changes , it does not seem to work. – David Frick Feb 27 '20 at 23:56
  • Can you be more specific? – Emiel Zuurbier Feb 28 '20 at 00:17
  • I am still not seeing the request happen on the backend. No POST being registered, the method being called. Anything being printed. See original answer edit for new code. The alert("Hello") is not being called. – David Frick Feb 28 '20 at 00:20
  • Ah, that makes sense. Well you need to trigger the `submit` event at the appropriate moment. Right now you create your element and dispatch the event, and then set the event listener. Without listening first, nothing is going to happen. It is not clear for me how the user lets you know that they are finished selecting the files. Do they only see the file select prompt? Or is there another way of letting you know that they are done with the files? – Emiel Zuurbier Feb 28 '20 at 00:25
  • So I copied the code from a previous JS project I was working on, whereby it submitted when the person finished typing which I realize does not apply here. I guess the conundrum is that when they click the picture button it opens what I am guessing is a OS specifc file uploader. I am not sure that I have a way to detect its submit, which does work....hmmmm – David Frick Feb 28 '20 at 00:32
  • I've edited my answer to include an alternative approach to listen for a trigger. Use the `change` event to upload your pictures. It will trigger whenever the user has selected their files. – Emiel Zuurbier Feb 28 '20 at 00:46
  • So that was what I need to trigger it! Thanks so much. Now I just need to edit the backend to get it to work. I was getting an error about accessing bytes, but I just need to play with it. When I print the request.file and request.files['files'] I get ImmutableMultiDict([('files', )]) I wonder how I would get that nice 'cat.jpg' text out of there. Let me run some example tests – David Frick Feb 28 '20 at 00:54
  • So I got it working. I will need to edit the backend a little bit for multiple pictures but I did successfully upload a single picture. Thanks very much mate, you saved me a lot of headache! Cheers! – David Frick Feb 28 '20 at 01:06
  • Followup question: How would I get JS to save the filename in a variable. var picture_name = picture_form["files"].value; alert(picture_name);` returns 'C:/fakepath/cat.jpg` Oddly enough, haha – David Frick Feb 28 '20 at 01:16