Screencast : How to Create a File Upload Progress Bar in Rails, Passenger, Prototype and Low Pro
1 comments
How to Create a File Upload Progress Bar in Rails, Passenger, Prototype and Low Pro from Erik Andrejko on Vimeo.
An upload progress bar is one of the best ways to improve the usability of file uploads in your application. This screencast will show how to create a file upload progress bar using Rails, Passenger, Low Pro and the upload progress bar apache module.
Screen Cast
- View a quicktime version: full, iPod.
- View the complete code for the screencast.
Required
- Passenger
- Upload progress bar apache module
- Prototype
- Low Pro for unobtrusive javascript
- Paperclip or something else to handle the uploaded files
Optional
- Configure a realistic (slow) development environment
- Passenger Prefpane a preference pane for Passenger under OS X Leopard
1. Install Apache Module
A module must be installed for Apache to respond to requests for the upload progress. The instructions for installing the Apache module can be found at Drogomir's blog.
Installing under Intel and Mac OS X
The default compile of the apache module will not work under Leopard. Apache will generate this error on startup:
httpd: Syntax error on line 489 of /private/etc/apache2/httpd.conf: Cannot load /usr/libexec/apache2/mod_upload_progress.so into server: dlopen(/usr/libexec/apache2/mod_upload_progress.so, 10): no suitable image found.
Did find: /usr/libexec/apache2/mod_upload_progress.so: mach-o, but wrong architecture
Use this command to install under Mac OS X
git clone git://github.com/drogus/apache-upload-progress-module.git
cd apache-upload-progress-module
sudo apxs -c -Wc,-arch -Wc,x86_64 -Wl,-arch -Wl,x86_64 -i -a mod_upload_progress.c
2. Configure Apache/Rails
In the httpd.conf
located at /private/etc/apache2/httpd.conf
:
LoadModule upload_progress_module libexec/apache2/mod_upload_progress.so
In the vhost config:
<VirtualHost *:80>
ServerName file-upload-progress.local
DocumentRoot "/Users/andrejko/Documents/Projects/web/ri/posts/code/file_upload_progress/public"
RailsEnv development
RailsAllowModRewrite off
<directory "/Users/andrejko/Documents/Projects/web/ri/posts/code/file_upload_progress/public">
Order allow,deny
Allow from all
</directory>
# needed for tracking upload progess
<Location />
# enable tracking uploads in /
TrackUploads On
</Location>
<Location /progress>
# enable upload progress reports in /progress
ReportUploads On
</Location>
</VirtualHost>
Important
Make sure to configure the environment.rb
file so that Paperclip will work under Passenger.
# location of the Image Magick command files
Paperclip.options[:command_path] = "/usr/local/bin"
3. Model
The model image.rb
has the standard Paperclip options
class Image < ActiveRecord::Base
has_attached_file :photo,
:styles => { :medium => "300x300>",
:thumb => "100x100#" }
validates_attachment_presence :photo
end
4. Controller
The controller is a standard restful controller:
class ImagesController < ApplicationController
def index
@images = Image.find(:all)
# generate a unique id for the upload
@uuid = (0..29).to_a.map {|x| rand(10)}
end
def create
@image = Image.new(params[:image])
respond_to do |wants|
if @image.save
flash[:notice] = 'Image was successfully created.'
wants.html { redirect_to(:action => 'index') }
else
wants.html { redirect_to(:action => 'index') }
end
end
end
end
5. View
<% content_for :scripts do %>
<%= javascript_include_tag 'upload' %>
<% end %>
<div class='images'>
<%= render :partial => 'image', :collection => @images %>
</div>
<div id='progress' style='display: none;'>
File upload in progress
<div id='bar' style='width: 0%;'>
0%
</div>
</div>
<% form_for :image, :url => "/images?X-Progress-ID=#{@uuid}", :html => { :multipart => true } do |form| %>
<%= hidden_field_tag 'X-Progress-ID', @uuid %>
<%= form.label :photo %>
<%= form.file_field :photo %>
<%= form.submit 'upload image', :class => 'submit' %>
<% end %>
6. Unobstrusive Javascript
The javsacript is all contained in the javascsripts/upload.js
file
// if this is the iframe
// reload the parent
Event.observe(window, 'load',
function() {
try
{
if (self.parent.frames.length != 0)
self.parent.location=document.location;
}
catch (Exception) {}
}
);
Event.addBehavior({
"input.submit:click" : function () {
$('progress').show();
//add iframe and set form target to this iframe
iframe = document.createElement('iframe');
iframe.name = "progressFrame";
$(iframe).setStyle({width:'0', height: '0', position: 'absolute', top: '3000px'});
document.body.appendChild(iframe);
$(this).up('form').writeAttribute("target", "progressFrame");
$(this).up('form').submit();
//update the progress bar
var uuid = $('X-Progress-ID').value;
new PeriodicalExecuter(
function(){
if(Ajax.activeRequestCount == 0){
new Ajax.Request("/progress",{
method: 'get',
parameters: 'X-Progress-ID=' + uuid,
onSuccess: function(xhr){
var upload = xhr.responseText.evalJSON();
if(upload.state == 'uploading'){
upload.percent = Math.floor((upload.received / upload.size) * 100);
$('bar').setStyle({width: upload.percent + "%"});
$('bar').update(upload.percent + "%");
}
}
})
}
},2);
return false;
}
})
Optional Enhancements
Here are few additional possible enhancements that were not shown in the screencast.
- Show time to completion of upload.
- Remove progress bar div from the view and insert dynamically with javascript.
- Allow multiple simultaneous uploads.
Credits
Intro music thanks to Courtney Williams via Podcast NYC.
Comments
Add Comment