August 06, 2011

AJAX fie upload

Uploading a file from the browser can only be done by using forms.  Even then, you should remember to set enctype='multipart/form-data' attribute on your form.  Form submissions require a page refresh.  If your app is AJAX-heavy you probably wouldn't want to refresh the page just for uploading files.

Here's what you need to do to submit a form with file inputs without refreshing the page:
  1. Add an invisible iframe to your page.  Give the iframe a name.  For example, your iframe might look like: <iframe name='submit-iframe' style='display: none;'></iframe>.
  2. Set the target of your form so that the form submission goes to the hidden iframe: <form method='POST' action='/upload' enctype='multipart/form-data' target='submit-iframe'>.
  3. Add an onload event handler for your iframe.  This event handler will be called every time the form is submitted.  You can then read the response from the iframe's body.  A JQuery app might do it like:
    $(myIFrame).load(function() {
      var responseText = this.contentDocument.body.innerText;
      // Do something with response text.
    });
Update: This can lead to some weird behaviour when a user navigates away from the app and then navigates back using the browser's Back button.  (Or if the page is refreshed.)  The iframe's onload event will be fired and your event handler will be called when you don't expect it to be called.  To work around this problem, I am clearing the iframe's content immediately after copying the text into a variable.  The event handler now looks like:
$(myIFrame).load(function() {
  var responseText = this.contentDocument.body.innerText;
  if (!responseText) {
    return;
  }
  // Clear the content of the iframe.
  this.contentDocument.location.href = '/img/logo.png';

  // Do something with response text.
});

16 comments:

  1. This works great in Chrome but when I checked it in Firefox this.contentDocument.body.innerText returned null. Is there a more universal way of writing that, I wonder.

    ReplyDelete
    Replies
    1. innerText isn't a standardized attribute. It was an IE attribute Microsoft added which Chrome and Opera copied for compatibility's sake.

      For it to work in every browser, you need to use textContent instead.

      Delete
    2. Oh I didn't know this. Thanks for the tip.

      Delete
  2. Hello again. The iFrame isn't getting written to in Firefox, but it does in Chrome! Why can't we write to an iFrame in FF?

    Summary: Step 2 of you blog post does not work in Firefox.

    ReplyDelete
  3. Me again. My second comment is wrong. The iFrame does get written to in Firefox.

    But I still can't access it like you're saying in Step 3. But I can in Chrome!

    Suggestions?

    ReplyDelete
  4. Ok I got it working. I used this jQuery:

    $('#myIFrame-id').contents().find('body').html();

    Thanks for the helpful post!

    ReplyDelete
  5. I should have said this to be clear:

    var responseText = $('#myIFrame-id').contents().find('body').html();

    ReplyDelete
  6. Glad you have fixed the problem. And thanks for sharing your code snippet too.

    ReplyDelete
  7. thanks....
    https://plus.google.com/u/0/115754798918267852004/posts/i7tEthTqmzG

    ReplyDelete
    Replies
    1. Thanks Jason. I'm glad you found this post useful. (I tried to comment on your Google+ post, but Google+ won't let me. Probably because I am not in your circles.)

      Delete
  8. Hi,

    $(myIFrame).load(function() is not working in IE. Any suggestion how can we do it in IE?

    Thanks,

    ReplyDelete
    Replies
    1. I'm not sure what the problem is, and I don't have a Windows computer to check. Sorry.

      Delete
    2. Reaching an element inside an iframe:

      1. get all the elements inside iframe:
      IE: var a=window.iframe_name.document //<iframe name="iframe_name"...
      CHROME: var a= $(".frame").contents();

      2. Reach it with jquery :
      $("selector_string",a) // a is defined in 1.

      I googled and found this solution. But i checked the browsers i wrote above, it worked. i don't know FF/OPERA/SAFARI...

      Delete
  9. This is going to be flaky since load() gets call before there's even data in the iframe sometimes. It's probably to print some javascript in the iframe to call javascript in the parent.

    ReplyDelete