jQuery AJAX Form Submission with IFrames

Craig Blanchette on Google+ on December 17th 2012

One problem I've come along with creating an AJAX application that works with Symfony2 or any time you need to upload a file is that you need to target an IFrame in order to keep the user on the same page. Here is how I solved that problem so that your code nor your controllers need to know about it. Here is a regular form:

<form action="update.php">
  <input type="text" name="name" /> 
  <input type="text" name="email" />
</form>

And here is a form with an IFrame:

<form enctype="multipart/form-data" target="iframe_target">
  <input type="file" name="image" />
  <input type="text" name="name" />
  <iframe name="iframe_target" src="about:blank"></iframe>
</form>

First, what we'll do is check to see if there's a target. If there is, then we'll go with the IFrame method, if not, just regular post: (just going to use form ID for now)

function AjaxSubmit( id, callback ){
    var $el = $('#' + id), target; 

    if( target = $el.attr('target') ){
        var $iframe = $('iframe[name=' + target + ']'); 

        if( $iframe.size() > 0 ){
            $iframe.unbind( 'load.ajaxsubmit' ).bind( 'load.ajaxsubmit', function(){
                var response = $.parseJSON($(this).contents().find('#response').text()); 
                callback( response ); 
            });    
            $el.submit();
        }
    } else {
        var action = $el.attr('action'); 
        $.post( action, $el.serializeArray(), callback ); 
    }
}

So when there is an IFrame needed, we attach an event to that IFrame. The unbind before it makes sure that if you submit another they will both end up being called. When the IFrame finishes loading, it will call the callback function. Now, you'll notice that it looks for a div with id "response." You'll have to code this into your response. For me, using Symfony2, a Twig template is very simple:

<!DOCTYPE html>
<html>
 <body>
  <div id="response">{{ response|json_encode }}</div>
 </body>
</html>

Or a few lines of PHP:

<!DOCTYPE html>
<html>
 <body>
  <div id="response"><?php echo json_encode( $response ); ?></div>
 </body>
</html>

And that's it. You could easily turn this into a jQuery plugin. Right now I'm using this with Backbone.js with a FormView class. There is a submit() method for each view that contains this same logic. If you want to be able to use the variables you had before the post, then use $.proxy:

... {
  this.varA = 'hello'; 
  this.varB = 'world'; 

  AjaxSubmit( 'myform', $.proxy( function(){
      alert( 'Form Completed! ' + this.varA + ' ' + this.varB ); 
  }, this ) ); 
}