multiple image upload with preview using ajax and jquery

04 Jul 2010

In this tutorial, we will use jquery to upload multiple images using ajax and show a preview of the images. We'll also use the paperclip gem to store the files. You can look at how to add paperclip to your project .

First we generate the image model and set it to use paperclip. We'll also create a photo controller

plain
>> script/generate model photo
>> script/generate paperclip photo photo
>> rake db:migrate
>> script/generate controller photos
ruby
# app/models/photo.rb
class Photo < ActiveRecord::Base
  has_attached_file :photo
end

Next, we setup the view and javascript for ajaxupload.

Again, I'm using haml instead of erb templates, and builder for creating DOM elements.

And since we're using prototype and jquery in the same page, we need to make sure that there's no conflict between the two libraries. Add the following code in the head tag of your page.

ruby
= javascript_include_tag :defaults
= javascript_include_tag 'builder', 'jquery-1.4.2.min', 'ajaxupload'

:javascript
  var $j = jQuery.noConflict();

And here's the view

ruby
# app/views/photos/new.html.haml
- form_tag photos_path, :method => :post do
  #photos
  #file_upload
    %label Add a Photo
    = file_field_tag :photo, '', :id => 'file_tag'
    = image_tag '/images/spinner.gif', :id => 'spinner', :style => 'display:none'
    = submit_tag 'Save Photos'

:javascript
  $j(document).ready(function(){
    new AjaxUpload('file_tag', {
      action: '/photo_preview?authenticity_token=' + encodeURIComponent(#{form_authenticity_token.inspect}),
      name: 'image',
      onSubmit: function(file, extension) {
        $('spinner').show();
      },
      onComplete: function(file, response) {
        $('spinner').hide();
        show_preview(response);
      }
    });
  });

  function show_preview(path) {
    var parent = Builder.node('div', {class:'preview'});
    var image = Builder.node('img', {src:path, width:'80px'});
    var hidden = Builder.node('input', {type:'hidden', value:path, name:'photo[]'});

    parent.appendChild(image);
    parent.appendChild(hidden);
    $('photos').appendChild(parent);
  }

Now we need to create the photo_preview and create action in photos controller

ruby
# app/controllers/photos_controller.rb
# of course, this code won't work if the user uploads different photos with the same filename
def create
  photos = params[:paths].collect {|p| {:photo => File.new('public' + p)}}
  @photos = Photo.create(photos)
  # now do whatever you wish with @photos
end

def photo_preview
  if params[:image]
    filename = params[:image].original_filename
    path = '/images/' + filename
    f = File.new('public' + path, 'w') {|f| f.write params[:image].read}
    render :text => path
  end
end