Ajax file upload: custom solution or ready plugin?
Nowadays, ajax is a standard on the most of the websites. There is a group of problems that we - web developers - usually solve with ajax. Form submission can be one of good examples. It's common procedure to implement form validation and data submission without refreshing a page. It’s piece of cake unless your form contains file input field.
XMLHttpRequest has some limitations. Inability to upload file is one of them. So if you have a form which contains file input you can't simply send your form data with ajax. One of the solutions is to remove this feature and send your form data in a old-school way. But what can you do in a situation when your client really wants to have this feature? Well… you need to roll your sleeves up and dive into code.
Don't reinvent the wheel!
Avoiding the reinvention of the proverbial wheel is a standard bit of received wisdom in software development circles.
XMLHttpRequest object was born more than a decade ago. The community of web developers around the globe have had a lot of time to solve most of the problems related to its shortcomings. If you google for a upload file by ajax, you'll get gazillions of hits. Most of them link to ready-to-use plugins. I must admit that I don't like to use plugins. There are three reasons why:
- First of all, if you use plugin, you will lose a chance to learn something new …most of the plugins are so easy to use that you don't even need to think how they works.
- Secondly, everything works fine until you need some sort of customization …I'll elaborate on it later in the article.
- Last but not least, most of the plugins involve a lot of unnecessary functions. When you need only one simple functionality, it doesn't make sense to use plugin that can do almost everything: decreasing global warming, saving the world and at the same time …importing kilobytes of unused code.
There is no discussion, ready-to-use plugins have at least one huge advantage: they save your time (obviously if everything works fine). Assuming that you don't have time to develop your own solution, let's try to find something that fits your needs.
jQuery Form Plugin
It might be one of the first plugins that you will find and may choose to give a try. Plugin documentation assures that plugin "allows you to easily and unobtrusively upgrade HTML forms to use AJAX". Great! It sounds nice! The documentation tells the truth. This plugin is really easy to use and well-documented, so you don't have to worry that you will spend hours trying to configure all options. It's sounds like you found ideal solution. Upps… wait a minute!
How can I configure the plugin to catch the errors thrown by server? I can see that I can catch some errors before sending data to web server but it's not the case. Well, the truth is that the plugin can't detect the response code because it doesn’t have access to the response headers. So, even if you send error status to the browsers, the plugin can't support it (here is a discussion on this topic). It's a bit of a hassle, isn't it? Undoubtedly, in the most of the cases you need to inform a user that something went wrong and his/her file wasn't uploaded. Don't worry, you have a lot of plugins to test.
Plupload
The name of the plugin sounds a little bit tricky but the website looks promising. In fact this plugin can do a lot for you. It supports errors handling, multipart upload, drag and drop stream upload etc. At the beginning it might be a little painful to configure all the features but plugin documentation is clear and have some useful examples. After spending some time playing with it you shouldn't have any problems to use it.
I evaluated plugin during one of our projects. I must admit I encountered some problems with it. In the project we had a sign up form. One of the input fields allowed user to upload an avatar image. Upload should have been initiated as soon as a file is selected. If upload is successful, user should have seen preview of avatar on the right side of the form …more or less like on Sortfolio …otherwise user should have seen an error message. To summarize, I needed following functionality:
- error handling
- ability to invoke some function after successful upload
- ability to fire up upload just after user selects a file
- playing gently with Ruby on Rails on the server side
The first version of my code, looked something like that:
//configuration
var uploader = new plupload.Uploader({
runtimes : 'html5,flash,silverlight,browserplus',
browse_button : 'logo',
max_file_size : '10mb',
url : '/logos',
flash_swf_url : '/javascripts/plupload.flash.swf',
silverlight_xap_url : '/javascripts/plupload.silverlight.xap',
multipart: true
});
//initialization
uploader.init();
//error handling
uploader.bind('Error', function(up, err) {
$('.signup_form form dl:eq(1) dd').after($('<dd/>').addClass('error').html('Image upload error'));
up.refresh(); // Reposition Flash/Silverlight
});
//success
uploader.bind("FileUploaded", function(up,file,response){
var resp = $.parseJSON(response.response);
$('.vendors img').attr('src', resp.logo_url);
$('.signup_form form').prepend($('<p/>').css('display', 'none').append($("<input/>").attr({name: "logo_id", type: "hidden", value: resp.id})));
});
As you can see there is nothing special in the code. I used only information you can find in documentation. However, it wasn't working as I expected. The first thing was that the upload didn't start after the file has been chosen. Fortunately, it was quite easy to solve:
//invoking start function on FilesAdded event
uploader.bind('FilesAdded', function(up, files) {
uploader.start();
up.refresh(); // Reposition Flash/Silverlight
});
The second problem was lack of sending authenticity token to the web server. Ruby on Rails demands that, so I had to find solution. Fortunately, when you configure uploader object, you can add same addition parameters to the request. So I changed my code to look like this:
var uploader = new plupload.Uploader({
runtimes : 'html5,flash,silverlight,browserplus',
browse_button : 'logo',
max_file_size : '10mb',
url : '/logos',
flash_swf_url : '/javascripts/plupload.flash.swf',
silverlight_xap_url : '/javascripts/plupload.silverlight.xap',
multipart: true,
multipart_params: {
"authenticity_token" : $('.signup_form form input[name="authenticity_token"]').val()
}
});
So far, so good. Everything worked fine in Firefox, Chrome and Safari. Unluckily, there was also Internet Explorer that, as you would expect, was trying to ruin everything. The plugin seemed not to work in IE. At the end I found out that plupload had a bug and you should also configure resize parameter in the uploader object (more about this option you can read in documentation). I didn't want to resize my image, so I set up this property as below:
var uploader = new plupload.Uploader({
runtimes : 'html5,flash,silverlight,browserplus',
browse_button : 'logo',
max_file_size : '10mb',
url : '/logos',
flash_swf_url : '/javascripts/plupload.flash.swf',
silverlight_xap_url : '/javascripts/plupload.silverlight.xap',
multipart: true,
multipart_params: {
"authenticity_token" : $('.signup_form form input[name="authenticity_token"]').val()
},
resize: {width : '100%', height : '100%', quality : 100} //to solve IE problem
});
After all of these additional changes the plugin started to work in the major browsers.
Let's dive into code!
Plupload plugin can be very really useful if you need a quick solution. Unfortunately, during one of our next projects I realized that under some circumstances it doesn't work as you would expect. Problems could be encountered when form was nested in a modal window. If you dive into Pluload's guts, you'll notice it creates a transparent input file field over an element that you use as browse_button. In this case, when the form was in the modal window, the transparent input wasn't positioned well. So, when user clicked on browse_button, nothing happened. There were three options: try to fix the Plupload, try to find another solution that cooperates with modal window or write your own solution. In my opinion, it was the highest time to write my own plugin
code quest ajax upload
No fluff, easy-peasy piece of code that has to meet two requirements.
- Give developer ability to decide what exactly should happen when a request is sent to the server - handle error or success response
- Work in the latest versions of all major browsers
Unluckily, my solution has one disadvantage. If you want to handle the errors from the server in all browsers, you must set up cookie on the server side if error was thrown. I know it could be a pain in the neck, but this is a good workaround for browsers without support XMLHttpRequest Level-2. Let me show you how you can use it.
In your JavaScript file place code based on the snippet below.
$('your_form').codequestAjaxUpload(
{success: function(response) {
//do something when success
},
errors: function(response){
//do something
},
ajaxStart: function(){
//what happens when ajax start
},
ajaxComplete: function(){
//what happens when ajax complete
}
});
On the server side (this is an example how you may set up cookie on the server side when error occurs):
def create
@item = Item.new(params[:item])
@item.member = @member
if @item.save
render :partial => 'members/item', :layout => false
else
cookies[:ajax_file_upload_error] = {:value => 'error', :expires => Time.now + 3600}
render :action => 'new', :layout => false, :status => :unprocessable_entity
end
end
When we handle an error on the client side, the plugin destroys the cookie. To be sure that the cookie was cleaned out before next request, set it's expires time for a short period.
That's it. I hope you enjoyed the article. If you like it, spotted mistake, room for improvement, or just would like to say hello please don't hesitate to drop a comment. Thank you!

Comments