Uploading Multiple Files using Safari 4's multiple='true' attribute

Posted by Daniel on 03/18/2009

Safari 4 and Selecting Multiple Files for Upload

I've long been in need of uploading multiple files via a web page. The truth of the matter is, HTML and HTTP aren't made for this. One file can be uploaded, but it's clumsy.

Websites have been creating workarounds for this type of feature for years. Some do it with Flash, which makes me quiver. Some do it by adding multiple file input fields to a form, which is not enjoyable for the end user. Safari to the rescue.

Apple announced the beta version of Safari 4 a week or two ago. So far, I've loved it. It looks great, and it is FAST! One of my favorite features it has implemented has been their support for selecting multiple files using one input field. This is done by simply setting an attribute on the input field of 'multiple'='true'.



Making the Magic Happen with Rails

I love Rails, it makes my life and the work I do easy. I also love the attachment_fu plugin. Paperclip is great for attaching a single file to an existing record, but one is the low number on he totem pole. I tend to use attachment_fu much more, as I typically break the 1:1 ratio Paperclips is so good at.

So, how do we make the magic happen? It's easier than you might think. We just add all of the selected files to an array that we iterate through in the controller, creating a new DB record for each one. Let's create that array in the view, and call it upload_data[]

<!-- app/views/assets/_form.html.erb -->
<% form_for @asset, :html => { :multipart => true } do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :file %><br />
    <%= file_field_tag 'upload_data[]', :multiple => 'true' %>
  </p>
  <p><%= f.submit "Submit" %></p>

<% end %>

Now, in the controller, let's go through that array and create a new record for each instance in the array that isn't blank.

def create
  #app/controllers/asset_controller.rb
  @asset = Asset.new()
  params[:upload_data] ||= []
  params[:upload_data].each do |file|
     @asset = Asset.create({:uploaded_data => file}) unless file ==""
  end
  flash[:notice] = "Successfully created asset."
  redirect_to @asset
end

Thats it! The above code will redirect to the last asset that was saved to the DB.

It's worth noting that you can add an upload progress bar if you're using nginx or Passenger. Since all the selected files are uploaded in one array, the progress bar would show the progress of all the files combined. Very cool.




But, is it practical?

I'm sure 90% of the people who read this will say, "Yeah, it's cool, but who uses Safari anyway?". This is true, a small portion of the whole uses Safari. BUT, if we all start implementing this (it degrades gracefully as far as I can tell), more browsers will start supporting it. Screw Flash for uploads, THIS IS PRACTICAL.

I look at it this way:
If my end users are surfing my site with Safari, then great, they get to upload multiple files at once. If they are on something else, tough crap, upload one file at a time. This takes 3 minutes (if I'm being a slow poke) to implement on a new site. A flash alternative is not as seamless, and that is pending they have a compatible flash client installed. Development time vs. benefits this feature presents makes it about 5000% more efficient than Iframe or Flash uploads. It keeps the code DRY.

Your turn, give it a go!

Comments.

No comments yet.

Name: (required)

Email: (not shown or published)

Website Url:


(Use Markdown Syntax, <pre> your code </pre>)