Saturday, January 29, 2011

Get JSON data response with JQuery Form plugin for a file upload form


My latest project involves JQuery for UI development. Frankly speaking, I did not have much idea of JQuery before. But I found it very easy to understand. All APIs are well documented with lots of examples available on Internet. The best feature of JQuery is its plugins. There are thousands of plugins available to play with. For every need of my web app I could find a plugin.



In my web app I am using JQuery Form plugin to submit the forms through AJAX. The return from my servlet is JSON object. The form plugin works great for all my forms. Well almost all, there is a HTML form which has a file upload box along with other input fields. This form was not able to get the response in JSON format. Though it worked in Google Chrome, but Firefox was presenting me with a file save dialog box.
I did a lot of googling to find out whats going on. Finally I observed that the problem is with HTTP headers that the form submit request has. Here are the headers captured by Chrome



Request headers captured by Chrome




As you can see the Accept request header does not contain application/json. That is why the response JSON object is not understood by the browser and it pops up a file save dialog.
When I checked the source code of JQuery Form plugin, I found that in case of a multipart form (i.e. form  having file items) the plugin does not use ajax to submit the form. Rather it uses an IFRAME element. So the form submission is a normal GET/POST request. Here is the part of plugin code that does it -

// are there files to upload?
 var fileInputs = $('input:file', this).length > 0;
 var mp = 'multipart/form-data';
 var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);

 // options.iframe allows user to force iframe mode
 // 06-NOV-09: now defaulting to iframe mode if file input is detected
   if (options.iframe !== false && (fileInputs || options.iframe || multipart)) {
    // hack to fix Safari hang (thanks to Tim Molendijk for this)
    // see:  http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
    if (options.closeKeepAlive) {
     $.get(options.closeKeepAlive, fileUpload);
  }
    else {
     fileUpload();
  }
   }
   else {
    $.ajax(options);
   }

Basically its a normal HTTP request not ajax call. So the Accept header is set to application/xml, text/html etc. But the response from the servlet has a Content-Type as application/json. So, browser opens a save as dialog.

To solve this issue in a proper fashion we need to modify the plugin code to upload the file through an ajax request. But files can not be transmitted through ajax. Due to this limitation I had to find another solution which is not as clean as the above one but it will do the job.

The solution is to set the response Content-Type as text/plain. To do this I used below code

if ((null != req.getHeader("Accept"))
                  && req.getHeader("Accept").contains("application/json"))
         {
            resp.setContentType("application/json");
         }
         else
         {
            resp.setContentType("text/plain");
         }
         resp.getWriter().write(jsonResponse);

I checked the request header Accept to see if browser can accept a JSON. If yes, then set the response Content -Type to application/json. Otherwise set it to text/plain. This technique will fool the browser as it falls among the requested types. After this change I captured the response headers

Response headers captured by Chrome



We need to make one more change in the client side code.

var options = { 
         dataType: 'json',
         success: processResponse
     }; 
$('#upload_form_id').ajaxForm(options);


This is important because, without the dataType property, the response will be treated as a text by the plugin. So, the client side script that works on the response of the form submit will not work as it will get a string instead of JSON object.

I checked the method in Firefox 3.6, Chrome 8.0, Internet Explorer 8 and Opera. It worked. I could also maintain my servlet's response type as application/json for all other forms (which does not have a file upload). I hope this works for you too.

11 comments:

  1. I've been trying to get my TinyMce plugin to upload a multipart image all morning on firefox. Thanks to your great post it now works!!

    I hadn't realised that I could treat the text response as JSON response in the plugin.

    ReplyDelete
  2. Good post! Thanks for your help...

    ReplyDelete
  3. jQuery Form Plugin doesn't seem to work with FF3.6?

    ReplyDelete
  4. @Whofeih : I use FF3.6 and 3.7 with jQuery Form plugin. It works fine. Please check your jQuery.js version. Mins is 1.4.4.

    ReplyDelete
  5. I've a task like, uploading a csv file and call a service and fill a grid with that data, which is of type json. I am using the same plugin, in FF and Chrome its working fine. But in IE, after uploading file, in return it's prompting to download the same file.

    $("#uploadbutton").click(function() {
    var filename = $("#fileToUpload").val();
    var opts = {
    data: {file: filename},
    iframe: true,
    url: 'myurl',
    contentType: 'application/json; charset=utf-8',
    type : "POST",
    dataType: "json",
    beforeSubmit: showRequest,
    success: function(data, statusText, xhr, $form){
    var thegrid = jQuery("#grid")[0];
    thegrid.addJSONData(data);
    }
    };
    $('#grid').submit( function {
    $(this).ajaxSubmit(opts);
    return false;
    });
    function showRequest(formData, jqForm, options) {

    var fileToUploadValue = $('#fileToUpload').val();
    var ext = $('#fileToUpload').val().split('.').pop().toLowerCase();
    if($.inArray(ext, ['csv','CSV']) == -1) {
    alert('invalid extension!');
    return false;
    }
    return true;
    }
    });

    ReplyDelete
  6. Hi Currently I face an issue wherein I have to do multi-part ajax file upload from a form and for that I am using bluImp File Upload Plugin version 4 with which I am not able to get the response json back from the Servlet in case of IE wherein I am having the upload logic. I have given the response type as in the if condition you have given. While trying to debug in IE9

    SCRIPT5007: Unable to get value of the property 'length': object is null or undefined

    ReplyDelete
    Replies
    1. I do not have much idea on the plugin that you mentioned. You can try with jquery-form plugin.

      Delete
    2. Hi
      I am using IE8 / IE6 and uploading file using jquery form plugin, the jersey web service sends json response, in the browser I am getting timeout error. Any inputs.

      I have tried to send the plain text from jersey web service, but the IE client displays 'Access is denied' error. Any inputs.

      Thanks in advance
      Senthil

      Delete
  7. This plugin supports same-origin-policy? I wanted to overcome with this 'XMLHttpRequest cannot load --/uploadtest.php/. Origin is not allowed by Access-Control-Allow-Origin' error which is caused because of same-origin-policy

    ReplyDelete
    Replies
    1. same-origin-policy is enforced by your browser. No plugin including this one can overcome that limitation.

      Delete