Sunday, February 17, 2013

Getting jQuery-File-Upload to play nicely with Rails 3.x and Internet Explorer

So you're a Rails programmer. You'd like to implement Ajax-flavoured file uploads on your app. You may well have skimmed your eye over Flash-based solutions like SWFUpload or Uploadify, grappled with such delights as the Rails CSRF token problem, and quickly decided that Flash-based solutions suck huge leathery balls.

Then you discover the HTML5-based jQuery-File-Upload. Dear god. It's amazing. Not only is it HTML-based, so the browser will happily pass the session and CSRF tokens with Ajax requests, but it seems to promise everything. Asynchronous file uploads. Multiple files. Graceful failure for older browsers. A nice GUI. Even IE6 compatibility! It'll wash your car and send flowers to your spouse and egg the houses of your enemies and promise you the world. Amazing stuff.

You implement it on your site and start uploading images - perhaps working alongside the fantastic Rails image manipulation gem Paperclip - and everything's working wonderfully. Chrome, Firefox, Safari, it works beautifully. And then you try it on Internet Explorer, and the file upload form returns the following error:

SyntaxError: Invalid character


Eh? Doesn't the plugin's Github page say it should work on IE6+?

Why yes it does. See, the problem isn't Internet Explorer itself; it's that Rails needs a bit of massaging before the two will play nicely. In particular, the CSRF token rears its ugly head once again. The thing is, Chrome, Firefox, Safari - they all natively support XHR file uploads, as noted on the documentation. IE doesn't. jQuery-File-Upload works around this by invisibly swapping in an iframe-based form behind the scenes and using that instead.

Now that's just fine and dandy when you're using a vanilla web server that doesn't have CSRF protection - but the iframe form doesn't carry the Rails CSRF token in its form data, so when the file upload happens, Rails notes its absence, and duly throws a tantrum.

How to fix this? Simple. You'll need to whack the CSRF token on the end of the URL you use when uploading a photo. Here's how. If you followed jQuery-File-Upload's main.js code to initialize the plugin, the initialization call itself will look something like this:

1
2
3
4
$('#fileupload').fileupload({
  dataType: 'json',
  acceptFileTypes: /(\.|\/)(gif|jpe?g|png|tiff)$/i
});


Simply add the following code below, so now you've got this:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
$('#fileupload').fileupload({
  dataType: 'json',
  acceptFileTypes: /(\.|\/)(gif|jpe?g|png|tiff)$/i
});

$('#fileupload').bind('fileuploadsend', function(event, data) {
  auth_token = $('meta[name="csrf-token"]').attr('content');
  data.url = data.url + '?authenticity_token=' + encodeURIComponent(auth_token); 
  $.blueimpUI.fileupload.prototype.options.send.call(this, event, data);
});


On the 'file upload send' event, this function fires, whacking 'authenticity_token=[auth_token]' on the URL, and then continuing calling the jQuery-file-upload code as normal, but now with an amended URL.

That, however, is only half the problem. Once you've done this - and be sure to use Ctrl+R to refresh the page in IE9, rather than your standard F5, to clear the cache and load your new improved code - you'll notice that the request itself works and the relevant Rails controller fires, but IE insists on downloading the JSON response as a static file. This is a known issue - to get around it, you'll need to change the content type to 'text/plain' in your Rails file upload controller response code. Like so:

1
2
3
4
def create
  # your existing code
  render :json => [photo_json], :content_type => 'text/plain'
end


And that's it! IE will now behave itself.

No comments:

Post a Comment