1

I'm playing around with the FormData API. Here is the fiddle I'm using - I'm trying to inject blobs into my form and submit via AJAX.

//ic0 and tc0 are canvases
//image0 and thumb0 are file inputs
function ajaxSubmit(){
    var fd = new FormData(document.forms[0]);
    ic0.toBlob(
        function(blob){
            fd.set("image0", blob, "image0.jpg");
        }, "image/jpeg", 0.7);
    tc0.toBlob(
        function(blob){
            fd.set("thumb0", blob, "thumb0.jpg");
        }, "image/jpeg", 0.7);
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "/ajax-upload/", true);
    xhr.send(fd);
}

Browser behaviour is a little ... odd:

Chrome 50 (in Ubuntu)

Not able to do it, gives:

Failed blob:https%3A//fiddle.jshell.net/uuid4 to load resource: the server responded with 404

But I thought FormData.set() was supported now? It seems to work with non-blobs?

Firefox 46 (in Ubuntu)

Doesn't seem work if FormData() object is not initialized with a DOM object that already has the necessary file inputs (appended normal inputs are posted however). FormData.set() does not seem to work with file inputs and blobs (you'll note that thumb0 is null in the payload despite calling fd.set("thumb0", blob, "thumb0.jpg") on it. You can verify that it's set by doing console.log(fd.get("thumb0")) just after setting. You'll also note that the payload of image0 is incorrectly your original image, not the resized canvas image.


It's inconvenient not being able to customise your multipart FormData with javascript. I'm a javascript noob - am I just doing something completely wrong here, or are these browsers not correctly supporting the FormData API? How can I submit image0 and thumb0 correctly in my fiddle?


Edit: OK, going to bounty this. I know there are big jquery-dependent libraries like blueimp out there, and I think they base64 the data rather than transmitting as a file input (plus I want to avoid jquery). I only have to support the latest Chrome or Firefox for this project and I'm really hoping I can keep the code as clean as the FormData API seems to suggest I might be able to. Can anyone successfully grab an image off a canvas and insert it into the POST payload as a file? I'd like to be able to access it in request.FILES in django.

Community
  • 1
  • 1
Escher
  • 5,418
  • 12
  • 54
  • 101
  • I might be wrong but shouldn't you send the files object to the server instead of the blob? fd.set('image',e.target.files[0]') for single file? Also make sure that the form is multipart – noa-dev Jun 09 '16 at 09:00
  • Looking at the request's `Request Headers` and the `Request Payload`, the images seem to be sent just fine. And the server seems to return a `404` and not `400` or something. – C14L Jun 09 '16 at 09:49
  • Shouldn't you make sure that it posts to your actual server first? Right now it posts to a domain of jsfiddle. – aross Jun 09 '16 at 10:14

2 Answers2

4

I think one thing you are missing here is the asynchronous nature of javascript. In your ajaxSubmit method -

function ajaxSubmit(){
    var fd = new FormData(document.forms[0]);
    ic0.toBlob(function(blob){
        fd.set("image0", blob, "image0.jpg");
    }, "image/jpeg", 0.7);                // this call is asynchronous
    tc0.toBlob(function(blob){
        fd.set("thumb0", blob, "thumb0.jpg");
    }, "image/jpeg", 0.7);                // this call is also asynchronous
    /*
       hence the below code may get executed before
          fd.set("image0", blob, "image0.jpg");
       this statement. which it does. 
       that is why the canvas files are not submitted.  
    */
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "/ajax-upload/", true);
    xhr.send(fd);
}

Try synchronising the code like this

function ajaxSubmit(){
   var fd = new FormData(document.forms[0]);
   ic0.toBlob(function(blob){
      fd.set("image0", blob, "image0.jpg");
      tc0.toBlob(function(blob){
         fd.set("thumb0", blob, "thumb0.jpg");
         console.log(blob);
         console.log("Just blobbed thumb0");

         var xhr = new XMLHttpRequest();
         xhr.open("POST", "fileuploadbackend.jsp", true);
         //xhr.setRequestHeader("X-CSRFToken", csrftoken);
         xhr.send(fd);
      }, "image/jpeg", 0.7);

   }, "image/jpeg", 0.7);
}

also as jornare has suggested you should comment out the line

URL.revokeObjectURL(img.src);

which is causing error in chrome. Invoke it after the loading image is done.

also you have multiple elements with same id,

<input id="image0" name="image0" type="file" />
<label for="image0">image0</label> 
<input id="image0" name="thumb0" type="file" />

this isn't creating problem in your code but the id's should be different.

UPDATE

Here is the working fiddle.

Note: change the request url to your corresponding url.

Follow these links to get more insight on asynchronous behaviour of javascript and how AJAX request callbacks are handled.

Community
  • 1
  • 1
Rajesh Jangid
  • 724
  • 6
  • 13
1

Looking through your fiddle, you were cleaning up the dataURL before it was loaded, and therefore the thumbnails did not load at all. (The 404)

I made an updated fiddle https://jsfiddle.net/tLqch5x2/3/

function handleFiles(e){
    var img = new Image();
    img.onload = function(){
        var width = 30;
        var height = img.height * 30/img.width;
        e.target.ic0.width = width ;
        e.target.ic0.height = height ;
        e.target.tc0.width = width/2 ;
        e.target.tc0.height = height/2;
        var ctx = e.target.ic0.getContext("2d");
        ctx.drawImage(img, 0, 0, width, height);
        ctx = e.target.tc0.getContext("2d");
        ctx.drawImage(img, 0, 0, width/2, height/2);
        URL.revokeObjectURL(img.src); //clean up memory here instead
    };
    img.src = URL.createObjectURL(e.target.files[0]);
    //clean up to prevent memory leak
    //URL.revokeObjectURL(img.src); //too soon
}

It still has a 404, but now from trying to post to a url that does not exist on jsfiddle.net. Please give it a try in your environment.

Note that there are also some other minor changes in both the html and the code. (improper naming the images, and no need to reference the existing form when creating the new form for submission)

jornare
  • 2,903
  • 19
  • 27
  • Thanks, obviously I know very little about memory management in javascript. The 404 is OK, I just wanted to inspect the request payload. In this case I didn't need to reference the existing form when creating a new one for submission, but the project I am working on has about 20 more fields that are populated from the form, hence why I use it to instantiate new `FormData`. – Escher Jun 09 '16 at 13:32