Ajax File Upload

Courtenay : April 2nd, 2007

Here’s how to upload without leaving the page. It’s not ajax, but it feels like it. Sorry for the misleading heading :)

First, install the respondsto_parent plugin. Read more about respondsto_parent This plugin lets you fake rjs from an iframe response. Doesn’t make sense? Don’t worry, it will later.

script/plugin install http://sean.treadway.info/svn/plugins/responds_to_parent

Now, in your page, let’s say it’s an article editing form.

<form method="post" action="/articles">
  Title: <input type="text" name="article[name]" />
  &#8230;

Forget about this form.

Add a new form somewhere like the sidebar, looks like this:

<div id="asset_list">
  <%= render :partial => &#8216;asset&#8217;, :collection => @assets %>
</div>
<form method="post" action="/assets.js" target="background-uploader" enctype="multipart/form-data" id="background-upload-form">
  File: <input type="file" name="asset[uploaded_data]" />
  <input type="submit" value="Upload" />
</form>
<iframe width="1" height="1" name="background-uploader" src="about:blank"></iframe>

So, we have a multipart form that points to assets.js with a target, a 1x1 iframe, and a file field. These are all required. Optional, the div with the asset list, but it’s helpful for the user to see all the assets, or perhaps the most recent.

Open up your asset controller and modify the create action.

def create
  @asset = Image.create! params[:asset]
  respond_to do |wants|
    wants.html { redirect_to asset_path(@asset) }
    wants.js do
      responds_to_parent do
        render :update do |page|
          page.insert_html :top, &#8216;asset_list&#8217;, render(:partial => &#8216;articles/asset&#8217;, :object => @asset)
          page.visual_effect :highlight, &#8220;asset_#{@asset.to_param}&#8221;
          page[&#8216;background-upload-form&#8217;].reset
        end # render
      end # responds_to_parent
    end # wants
  end # respond_to
rescue ActiveRecord::RecordInvalid
  # eep! you&#8217;re screwed!
end

What does this do? Well.. first, it creates the asset. Remember how we pointed the form to “assets.js”? This tells rails that we wants.js and rails sets the content-type accordingly. This has the benefit of allowing us to create assets with the same action regardless of iframe or as before.

Unfortunately, since this isn’t ajax, it’s going to dumping its contents as text in the iframe and it won’t work. Enter the responds_to_parent plugin, which does some magic and makes our rjs response activate as expected.

Inside the responds_to_parent block we insert the new asset partial in the top of the list, flash it so the user is aware, and reset the upload form.

And that’s it. Error checking is left to the reader :)

11 Responses to “Ajax File Upload”

  1. xandr Says:

    It’s just me, or in Safari it opens a new browser window?

  2. Michele Says:

    This is a very clever way to do it! :)

  3. Mr. Kahn Says:

    Safari will ignore the iframe, and open in a new window, if the iframe is invisible, display = none, or the height is 0. Try increasing the height and width of the iframe first. Then try giving a regular width and a height of 1.

  4. court3nay Says:

    Exactly like I said :)

    So, we have a multipart form that points to assets.js with a target, a 1x1 iframe, and a file field. These are all required.
    
  5. xandr Says:

    Mr. Kahn, you are right, my iframe had a 0 height. Thank you.

  6. Outline Says:

    I’m not sure If i understand the assets.js correctly - what exactly is it? is it an empty file?

  7. Jason Says:

    /assets.js is not a file, though it looks like one. This tells Rails (if there is no public/assets.js file :P) to call AssetsController#index with the request type as JS.

  8. Mike Says:

    This is my Assets controller:

    class AssetsController < ApplicationController def create @asset = Asset.new @asset.uploadeddata = params[:uploadeddata] @asset.save

    respond_to do |wants|
      wants.html { redirect_to asset_path(@asset) }
      wants.js do
        responds_to_parent do
          render :update do |page|
            page.insert_html :top, 'asset_list', render(:partial => &#8216;stores/asset&#8217;, :object => @asset)
            page.visual_effect :highlight, &#8220;asset_#{@asset.to_param}&#8221;
            page[&#8220;background-upload-form&#8221;].reset
          end # render
        end # responds_to_parent
      end # wants
    end # respond_to
    

    rescue ActiveRecord::RecordInvalid # eep! you”re screwed! end

    def index

    end end

    I’m still getting a routing error for get or post on /assets.js

    /assets/create responds as an action but not /assets.js

    What did I miss?

  9. rense Says:

    Does anyone have a mirror of the responds_to_parent plugin? sean.treadway.info seems to be down.

  10. rense Says:

    never mind, found it at koders dot com.

  11. Bobby Says:

    I can’t get this to work. I keep getting a routing error from “/leadsheet.js” which is my controller. How do we specify a particular action, and make it look to rails like a javascript call? I like the theory of the post, being able to dispatch on request type, but I can’t get it to work for me. Please help.

Sorry, comments are closed for this article.