Single Button Ajax File Uploads (Using JQuery)


In the quest for ever more client side UI interactions in our web apps, one of the least understood elements in any web-dev’s arsenal is the file upload element. When it comes to Ajax like behaviour most people immediately turn to a 3rd party toolkit (Such as Teleriks Rad Controls) or they put together some small elements that match the other styled elements using flash and perform the upload that way.

With a little JQuery magic however it’s relatively easy to perform the same thing client side using nothing more than standard HTML 4 methods.

The biggest issue you’ll find is security. Because of its very nature the file input element has one of the strictest security models going (after all you wouldn’t want anyone being able to upload files to your site in an XSS attack would you?).

Because of this security there is no API or script access to allow you to automate the upload process, and even more so different browsers handle things slightly differently too, in IE for example issuing a ‘myfile.click()’ will cause the file element with ID myfile to open it’s browse dialog, in FF this call is ignored.

There’s also the CSS issue, you simply do not have the access to style the control in the same way that you can style other input controls.

So what can we do about it?

Well , we first need a button which will be the visible button and which we can style to fit the rest of our UI, we can achieve this just using a standard <input type=”button”> element or it’s more flexible cousin the <button> element.

Then we also need our <input type=”file”> element to browse and upload the file, and of course a <form> element to wrap it in.

In order to keep these neat, we’ll wrap them in a <div> element, and here’s the trick….

if we put the button and the file upload into our div in the correct order, and then use a couple of CSS rules to hide the file input and allow our button to show through, we can then have what appears to be just our style-able button, and nothing else…

So far so good?? Not quite, there’s one more thing we need to do.

We can’t fully hide the file control so instead we reduce its opacity to 0, this effectively makes it invisible, but still leaves it in place in the document hierarchy which (and this is the important bit) means that it still reacts to clicks on itself by the end user, we then blow the font size of the button up so the button fills the div that we’ve placed everything in.

The net result is that when you click on the div/button you appear to be clicking on the style-able button, but instead your still clicking on the invisible file input control. This then causes our browse box to pop up, and gives the impression that the file browser was triggered by a click on our style-able button.

by this point if your still following me, you should have something like this:

<style>
.file_input_div
{
  position: relative;
  width: 100px;
  height: 20px;
  overflow: hidden;
  margin-bottom: 2px;
}

.file_input_hidden
{
  font-size: 100px;
  position: absolute;
  right: 0px;
  top: 0px;
  opacity: 0;
  filter: alpha(opacity=0);
  -ms-filter: "alpha(opacity=0)";
  -khtml-opacity: 0;
  -moz-opacity: 0;
}

.file_input_button
{
  width: 100px;
  height: 17px;
  font-size: 0.7em;
  border: 1px solid black;
}

.hidden_iframe_div
{
  width: 0px;
  height: 0px;
  visibility:hidden;
}

.hidden_iframe
{
  visibility:hidden;
  width: 0;
  height: 0;
  border: 0;
}

#btnPhotoUpload
{
  color: white;
  background-color: green;
}
</style>

<div class="file_input_div">
  <button id="btnPhotoUpload" name="btnPhotoUpload">Upload a File</button>
  <form action="postfile.html" method="post" id="frmUpload" enctype="multipart/form-data">
    <input type="file" id="myFile" name="myFile" class="file_input_hidden" />
  </form>
</div>

Now we’re starting to get somewhere.

The second major problem we have using this method comes from the form post itself, because there is no API on the file input control we can’t tell it to send the file independently of what we are doing, and so we actually need to submit the small form tag it’s embedded in, because we are using a form tag this means that we have to do a full form post back, and in our nice Ajax UI this IS going to cause the entire page to be replaced by the result of our upload operation.

What we need is a way to hide this, but still make things think that it’s worked as normal, it’s exactly this kind of situation that we can use a hidden div containing an iFrame element for.

However, here’s an important lesson… we don’t want to be adding the iFrame tag to our HTML source, because this then opens our page up to other types of JavaScript attacks, and may allow a mean web hacker to inject extra pages of content into our site.

This is where we see the first appearance of our friend JQuery.

All we put into our HTML is another

<div></div>

tag, and then give it an ID we can assign to and work with. We then use JQuery to inject the HTML code to build the iFrame dynamically and then use it to capture the results of the form, all we simply need is something like:

<div id=”iFrame” class=”hidden_iframe_div”></div>

added just after the div holding our input controls.

I’ll come back to the JavaScript in just a moment…..

The last thing we need, is to know when a file name has been selected, thankfully there is one thing that has not been crippled on the file input control and that’s the ‘onChanged’ event, when a file is selected in the folder browse box, and then OK is clicked, the event handler for ‘onChanged’ is fired and the file name is inserted into the invisible input field above just as though it was functioning as a proper input field.

By trapping this event we can use it to trigger all of our JavaScript and JQuery functionality and perform the upload.

The first thing we need, is a document ready handler:

$(document).ready(function (){

Then we follow this with the event handler for the file controls on change event:

$('#myFile').change(function () {

Then we need to inject the iFrame into our iFrame div:

var iFrame = $('<iFrame name="postFrame" id="postFrame" class="hidden_iframe" src="about:none" width="0" height="0" border="0" />');
$('div#iFrame').append(iFrame);

Tell our small form where it should target the submit results (In this case our iFrame):

$('#frmUpload').attr("target", "postFrame");

And finally we submit the form all of this sit’s in:

$('#frmUpload').submit();

the final part of the puzzle is to know when the form has been posted, and the results have come back, we can do this by attaching a document load event handler using JQuery to our dynamically injected iFrame, and then waiting for that to trigger:

$('#postFrame').load(function () {

What you do in this function is entirely dependent on how you want to use your file upload, remember the results from the submit will not be seen and so you’re going to have to devise some method of communicating with your app, you can’t use normal ideas such as Json, because you don’t get the document contents assigned to you in a nice easy to use variable, they get dropped into your previously created iFrame element.

I choose to return a small fragment of HTML simply containing 2 <P> tags, into each tag on the server side is injected a status flag indicating if the upload failed or not, and a space for a message indicating the failure reason.

JQuery was then again employed to grab the text from these tags into 2 variable which could then be used.

The final JQuery based script looks something like this:

$('#myFile').change(function ()
{
  var iFrame = $('');
  $('div#iFrame').append(iFrame);
  $('#frmUpload').attr("target", "postFrame"); // Set the forms target for output to our iframe
  $('#frmUpload').submit(); // and submit it

  $('#postFrame').load(function ()
  {
    error = $('#postFrame').contents().find('#error').text();
    message = $('#postFrame').contents().find('#message').text();
    if (error == "YES")
    {
      alert(message);
    }
    else
    {
      alert('File uploaded successfully');
    }
      $('div#iFrame').html("");
      // Here you might do an ajax post to some other control or ajax update function to update your form
    });
  });

An extra point worth noting, to prevent leaving any old iFrame’s lying about taking up memory in the DOM, I’ve cleared out the previously injected iFrame from the div holding it.

Depending on what your uploading also, you may want to put some code to update your UI after the call to clear out the iFrame, in my case I was uploading a photo, so I made a final call using JQuery’s AJAX functionality to reload the img tag holding the photo (and a few other elements) to show the new photo that had been uploaded. What you do however is dependent on your app.

Remember also that the action in your form MUST be able to handle post requests, with all the security in browsers and web-servers these days it is easy to forget that you still need to think about things like cross site scripting and authorisation of web methods

I’ll not repeat the code for that here though, there’s plenty of examples in the API docs on the JQuery website.

I’ll leave you all with the full script in all its glory, enjoy and happy AJAXing

Shawty


You can download the code here

<!DOCTYPE html>
<html>

  <head>
    <title>Single button Ajax upload</title>
    <script src="jquery.js" type="text/javascript"></script>

    <script>

    $(document).ready(function ()
    {
      $('#myFile').change(function ()
      {
        var iFrame = $('<iFrame name="postFrame" id="postFrame" class="hidden_iframe" src="about:none" width="0" height="0" border="0" />');
        $('div#iFrame').append(iFrame);
        $('#frmUpload').attr("target", "postFrame");
        $('#frmUpload').submit();
        $('#postFrame').load(function ()
        {
          error = $('#postFrame').contents().find('#error').text();
          message = $('#postFrame').contents().find('#message').text();
          if (error == "YES")
          {
            alert(message);
          }
          else
          {
            alert('File uploaded successfully');
          }

          $('div#iFrame').html("");
          //$.post('bengalispiggot.cheese', null, function (data) { $('#photoForm').html(data); }, "html");
       
        }); // End iframe load handler
      }); // End file element change event handler
    }); // End document ready

    </script>

    <style>

    .file_input_div
    {
      position: relative;
      width: 120px;
      height: 85px;
      overflow: hidden;
      margin-bottom: 2px;
    }

    .file_input_hidden
    {
      font-size: 100px;
      position: absolute;
      right: 0px;
      top: 0px;
      opacity: 0.5;
      filter: alpha(opacity=0.5);
      -ms-filter: "alpha(opacity=0.5)";
      -khtml-opacity: 0.5;
      -moz-opacity: 0.5;
    }

    .file_input_button
    {
      width: 100px;
      height: 17px;
      font-size: 0.7em;
      border: 1px solid black;
    }

    .hidden_iframe_div
    {
      width: 0px;
      height: 0px;
      visibility:hidden;
    }

    .hidden_iframe
    {
      visibility:hidden;
      width: 0;
      height: 0;
      border: 0;
    }
    
    #btnPhotoUpload
    {
      color: white;
      background-color: green;
    }
    
  </style>
  </head>
  
  <body>
    <h1>Single Button Ajax upload</h1>

    <div class="file_input_div">
      <button id="btnPhotoUpload" name="btnPhotoUpload">Upload a File</button>
 	    <form action="postfile.html" method="post" id="frmUpload" enctype="multipart/form-data">
        <input type="file" id="myFile" name="myFile" class="file_input_hidden" />
      </form>
    </div>

    <div id="iFrame" class="hidden_iframe_div"></div>

  </body>
  
</html>
Advertisements

7 thoughts on “Single Button Ajax File Uploads (Using JQuery)

  1. Is it possible to get a complete demo of this concept? I have implemented the code but after selecting the file I don’t get any results

    • Hi Kyle,

      wow!! It’s been over 2 years since I wrote this post 🙂 If I still have the original code then sure no problem, however it may take me some time to find it, and if I don’t I’m afraid I don’t really have time to sit down and write a new copy.

      Alternatively, if you want to contact me on twitter, feel free to follow me, then we can exchange E-Mails etc, once we can speak direct I’m more than happy to spend 5 mins to take a look at your code and see if I can spot where your problem is.

      Regards
      Shawty

    • Hi Kyle,

      Following up from your comment, I went through the post and found quite a bit of broken HTML, I’ve now fixed all this and as a result you should now be able to see the html tags that where missing from the text in my post where I described things.

      I’ve also put a link to a zip on my skydrive so you can download the sample code (although I don’t know how reliable it will be, the links have a habit of timing out) hopefully that should allow you to get your code working.

      Regards
      Shawty

  2. i need this form upload action to trigger upon an font-icon… my technolgies limit to jquery and html only… can u help me in that

    • Using the technique I demonstrate you can put any element you like over the top of it.

      Just because I’ve used a regular styled button to trigger the upload, it doesn’t mean you have to. If you want to use a picture, icon, element, div or whatever all you simply have to do is substitute the markup for the button with the markup you need to produce your control.

      Once this is done you then just need to hook up the javascript functions to the appropriate click handlers in the same way I’ve done in the code presented in this post.

      One point I will make though, you say your choices are limited to JQuery & HTML only, if by that comment you mean you have NO server side support then no, it can’t be done. To do any kind of file upload you need support both on the client and on the server.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s