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 :)






URL encoding in Java

Encoding URLs in Java is quite trivial. However, too often I see people using the URLEncoder class for this. This is WRONG.

The URLEncoder class is used for encoding form data into xxx-form-urlencoded -format. Even though very poorly named, it does say this in the Javadocs ;)

The proper class for URL encoding is the URI class.

Lets assume we have an URL such as http://www.somedomain.com/shop/Blue Widgets

If you encode this with the URLEncoder class, you get:

http%3A%2F%2Fwww.somedomain.com%2FBlue+Widgets

Unreadable and incorrectly encoded. Reserved characters such as : and / should not be encoded. Also, the URLEncoder encodes empty space as "+" even though it should be encoded as "%20".

With the URI class, you get:

http://www.somedomain.com/Blue%20Widgets

Which is correct.

To construct as simple URL like the example above with the URI class:

URI myURI = new URI("http", "www.somedomain.com", "/Blue Widgets");

And to get the URL in an encoded format:

String url = myUri.toASCIIString();


JBoss and UTF-8 character encoding

Often you might face problems with character encoding, either in page content, form data or URL params. Data stored to or retrieved from a database doesn't show correct characters, submitted form data and query parameters are shown incorrectly.

Usually it's recommended to use UTF-8 encoding wherever possible. Here's how to set up JBoss so that it really really uses UTF-8 everywhere.

First of all, in your JSP/JSF pages set the HTTPRequest encoding as follows:

<%
   request.setCharacterEncoding("UTF-8");
%>

I usually also set the encoding via a JSP tag just to make sure:

<%@page contentType="text/html; charset=UTF-8"%>

Next, add a META tag to your HTML so that the browser (and search engines) also know your encoding:

<head>
   <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
</head>

Is that it ? No. JBoss encodes URL parameters with ISO-8859-1, so you need to tune the server.xml file located under /deploy/jboss-web.sar/. To the <connector> element (for either HTTP or AJP or both) you need to add URIEncoding="UTF-8".

Lastly, if you're using a database such as PostgreSQL, remember to create your database with UTF-8 encoding.