Github Twitter 2 Delicious Linkedin Stackoverflow Google plus new 32px Rss

File Uploading over AJAX using HTML5

Using the HTML5 File API to upload files over AJAX is a great way to to add file upload capabilities and not resort to using a flash based solution or iframe hacks.

It's easy to use and works with most modern browsers. This example use the Data URI Scheme to transfer the file over AJAX.

The mime type and base64 encoded file are sent as a standard parameter on a POST request. The server then decodes the image and saves it.

Data URI Scheme

An image can be embedded in a HTML or CSS document using this method. The example below is an encoded red dot image. The browser is just reading the embedded image as it's src attribute (scroll to see the entire URI).

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==

Client Side

On the client side your going to need to bind a change event to the file attachment field. The change event loops over all the files chosen, creates a FileReader object which uses the readAsDataURL method to encode the file using the Data URI Scheme. An object containing the orignial filename and data is then added to an array.

When the form is submitted each file in the array is read and sent across in an AJAX call.

Client side javascript
    var files = [];

    $("input[type=file]").change(function(event) {
      $.each(event.target.files, function(index, file) {
        var reader = new FileReader();
        reader.onload = function(event) {  
          object = {};
          object.filename = file.name;
          object.data = event.target.result;
          files.push(object);
        };  
        reader.readAsDataURL(file);
      });
    });

    $("form").submit(function(form) {
      $.each(files, function(index, file) {
        $.ajax({url: "/ajax-upload",
              type: 'POST',
              data: {filename: file.filename, data: file.data},
              success: function(data, status, xhr) {}
        });      
      });
      files = [];
      form.preventDefault();
    });
  

Server Side

The AJAX endpoint on the server just has to read the data and reassemble the file. Since we only want the actual file data we need to cut away the mime type and encoding. So we just look for base64, and take everything after it.

Sinatra endpoint
    post "/ajax-upload" do
      data = params[:data]
      filename = params[:filename]

      ## Decode the image
      data_index = data.index('base64') + 7
      filedata = data.slice(data_index, data.length)
      decoded_image = Base64.decode64(filedata)
      
      ## Write the file to the system
      file = File.new("public/uploads/#{filename}", "w+")
      file.write(decoded_image)

      "/uploads/#{filename}"
    end
  

Example Application

If you wanna just checkout a complete working example head on over to Github, clone the repo and run:
$ bundle install && bundle exec thin start

https://github.com/nick-desteffen/html5_ajax_file_upload_demo

Related Links

Comments

Joe
Joe says:
07/27/2012 11:33am

The demo seems cant run on my machine. eventmachine gem got errors when I try to install it.

BTW, is there any tutorial that about upload multi-file at once by using carrierwave + some java uploader?

Nick DeSteffen
07/29/2012 04:37pm

Joe,
I'm not sure what error you are having. The demo uses Thin for a server, you might be missing some libraries. If you post the error I might be able to help you out, but without it I really don't know.

Nick

meagar
meagar says:
05/28/2013 04:52pm

Note that this will open as many simultaneous connections as you select files, or as your browser supports. You should probably not be using $.each here, as the body of each iteration is asynchronous.

Nick DeSteffen
05/31/2013 04:52pm

@meagar -- correct, you would probably want to limit it to 1 file or put in some client side validations to ensure that only a few files could be uploaded at a time.

Oscar
Oscar says:
10/22/2013 11:21pm

Hi guys!

About prevent using $, I would like to know what do you think about sending the file content as a string with delimiter...

Thanks!

papucho
papucho says:
03/24/2014 07:36pm

Thank a lot. This saved me a lot of time!

Vadim
Vadim says:
07/18/2014 02:10pm

thanks a lot for great post!

suresh atta
12/19/2014 06:48am

Good one ...

koundinya b
koundinya b says:
03/03/2015 02:57am

Hi, Is there any limit to file size and if there is how to change it.

Dani says:
03/17/2016 08:48am

Thanks!! Works for me.

Format using Markdown