Friday, April 27, 2012

Asynchronous file upload with JQuery

Uploading files from a web page without having to reload the page, e.g. asynchronous file uploading, can be achived using AJAX. I used this in a project of mine and thought I'd write about it since it took some time to get it right.

I used the Blueimp JQuery File Upload widget. Out of box, it enables sending multiple files via one upload widget, a very nice UI and other neat features. Since I had no need for the built-in UI, I grabbed the basic plugin version and built my own UI from that. I also needed to have several upload widgets created on dynamic content. Out of box, the basic version works so that when a file is chosen its immediately uploaded, which isn't what I wanted so I also extended the plugin with programmatic uploading.

Basically you need two files, jquery.fileupload.js and also the jquery.iframe-transport.js that you can find in one of the packages from the Blueimp project site. Add those two to your page's header:


<head>
   <script src="fileupload.js" type="text/javascript"></script>
   < script src="jquery.iframe-transport.js"></script>
</head>

For the purposes of this post, I'm using a simple case of an intranet with dynamic content where you need to upload files into items on the page.

Let's assume you have a page that fetches data from a database and creates a list. I'm using Java for this example:


<%
for (int i = 0; i < myItems.size(); i++) {
Item item = myItems.get(i);
%>
Upload file for: <%=item.getName()%><br><br>
<input class="myFileClass" id="myFile_<%=item.getId()%>" type="file" name="theFile"><br /><br />

<div id="uploadButtonLayer_<%=item.getId()%>"><input type="button" class="uploadButton" id="uploadButton_<%=item.getId()%>" value="Upload"></div>
<div id="uploadProgressLayer_<%=item.getId()%>" style="display:none"></div>
<%
}
%>

Lets break this down. We loop through whatever items. First, create a file input element that will be used for the Blueimp plugin's widget. The elements needs an explicit ID, which we generate from the items' IDs. Also, to identify all the input fields give them a class that we can reference from JQuery (myFileClass).

Next we add a layer for the submit button and the progress. Each also need an explicit ID, again generated from the items' IDs.

To the button layer, insert a simple button, again with an explicit ID.

Next, the JQuery part, comments included:



// Create a widget from each input element
$('.myFileClass').fileupload({
// The file parameter name that is passed to the handler page
paraName: 'image',
// URL for the page handling the post
url: 'some_handler_page',
// Format for return value from the handle rpage
dataType: 'json',
// Limit selected files 1
maxNumberOfFiles: 1,
// If true, replaced the input element, which means the selected file's name won't be displayed.
replaceFileInput: false,
// Event handler for when a file is selected. We override it here so it won't be immediately uploaded
add: function(e, data) {
// Here we grab the ID of the item this upload widget belongs to (myFile_xxxx)
var id = $(e.target).attr('id').substring(7, $(e.target).attr('id').length);
// Pass the ID to the handler page as a parameter
$(this).fileupload('option', { formData: { 'itemId':id } });

// Now, bind a click handler to the item's upload button
$('#uploadButton_' + id).on('click', function() {
// Grab the ID from the upload button again (uploadButton_xxxx)
id = $(this).attr('id').substring(13, $(this).attr('id').length);
// Hide the upload button so that user won't click it more than once for this upload
$('#fileUploadButton_' + id).hide();
// Show the progress layer. Get a 'loader.gif' of your own if you want some nice animation :)
$('#fileUploadProgress_' + id).show().html('<img src="/img/loader.gif">&nbsp;&nbsp;Uploading, please wait ...');
// Submit the data.
// JQuery stores the data in the context of this widget and it's button, so that even though
// there are multiple widgets and buttons on the page, the correct data is submitted.
data.submit();
// Unbind the click event from the button. If not unbound and the user sends another file through
// the same widget, the previous file will also be sent
$(this).unbind('click');
});
},
// Basic AJAX success event that fires when the handler page is ready and returns something.
// For this example, the handler page returns JSON data containing the ID of the item
success: function(data) {
// Show the upload button
$('#uploadButtonLayer_' + data.id).show();
// Hide the progress layer
$('#uploadProgressLayer_' + data.id).hide();
},
// Progress event that fires every 100 milliseconds or so
// Used to calculate upload progress
progress: function(e, data) {
// Grab the ID of the item from the event (the widget is the target)
var id = $(e.target).attr('id').substring(7, $(e.target).attr('id').length);
// Calculate progress
var progress = parseInt(data.loaded / data.total * 100, 10);
// Update the text on the layer.
$('#uploadProgressLayer_' + id).html('<img src="/img/loader.gif">&nbsp;&nbsp;' + progress + '%');
}
});

If you need help or more detailed explanation please leave a comment and I'll try to answer asap.

Coding of the upload handler page is up to you. It's language dependent anyway :)






No comments:

Post a Comment