Problem

You want to be able to create a new row via AJAX on one of your database driven tables in your rails app. Additionally, you want to ensure that the entire table (in your view) is not reloaded when adding a single row to the table.

Solution

Luckily, with Rails + RJS, this problem isn’t too difficult. For this example, let’s pretend we own a bunch of bike parts and we want to be able to keep track of them in a simple inventory table. For even more simplicity, let’s keep the database using only one table.

Let’s assume you have already created your rails app and configured your database.yml. With the new app ready, let’s setup our migration. First generate the migration file:

In your app root:

script/generate migration AddComponents

This will create your migration file in app/db/migrate/xxx_add_components.rb, open it up and enter the following migration code to create our components table and fields.

class AddComponents < ActiveRecord::Migration
    def self.up
        create_table "components", :force => true do |t|
          t.column "created_at", :datetime
          t.column "make", :string
          t.column "model", :string
          t.column "category", :string
        end
    end

    def self.down
      drop_table :components
    end
end
Now migrate your data. In your app root:
rake db:migrate

Before we go on, first check your db to make sure that your table and fields were created.

With the Components table in your db, we can now generate our model so we can access the data (although we won’t be putting any code in there for this tutorial):

script/generate model Component

Now generate the controller so we can handle all of our actions:

script/generate controller Inventory

Open up the app/controllers/inventory_controller.rb and let’s add an entry page and an instance variable that will eventually allow us to see all of our bike components on one page.

def list
    @components = Component.find(:all)
end

Now, we can create and edit to our view (app/views/inventory/list.rhtml) which will present our table of components and a form to add a new component:

<h1>Bike Components Inventory</h1>
<h4>Example: Adding rows to a table via AJAX (using RJS)</h4>

<table id="components">
    <tr>
        <th>Make</th>
        <th>Model</th>
        <th>Type</th>
    </tr>
    <% @components.each do |c| %>
        <%= render :partial=> "bike_component", :locals=>{:c=>c} %>
    <% end %>
</table>
<p><a href="javascript:new Effect.toggle('newComponent','blind',{duration:0.1});void(0);">Add a new component</a></p>
<div id="newComponent" style="display:none">
    <% form_remote_for :component, :url=>{:action=>:add_row}, :html=>{:id=>"component-form"} do |f| %>
        <p><label id="make">Make: <%=f.text_field :make %></label></p>
        <p><label id="model">Model: <%=f.text_field :model %></label></p>
        <p><label id="category">Category: <%=f.text_field :category %></label></p>
        <p><%= submit_tag "Save" %></p>
    <% end %>
</div>

Let’s dissect this a bit:

  • The layout consists of a simple HTML table and below it is a hidden div that is toggled via javascript when “Add a new component” is clicked on.
  • For each component row in our database table (the @components instance variable that we created in our controller), we create a row in our HTML table and rendering it using a partial. For the partial, we must pass in the component object and store it as the local variable “c” (more on this later).
  • We have a form_remote_for to handle the AJAX form submit that will trigger the “add_row” AJAX method that we are about to write. (FYI, formremoteform pretty much works the same way as form_for but submits it the data via AJAX)
  • We give the form an ID “component_form” so that we can reference it through the DOM using RJS.

Let’s now create and fill in the _bike_component.rhtml partial which will render each component row in the table:

<tr id="component-<%= c.id %>">
    <td><%= c.make %></td>
    <td><%= c.model %></td>
    <td><%= c.category %></td>
</tr>

Note that we are giving the row and ID of “component-xxx” so that we can also reference this using RJS.

So why did we use this partial rather than rendering it right in the list.rhtml? We did this so that we can be DRY (Dont’ repeat yourself) and reuse the above code when we are adding rows via RJS. This will make more sense after we write the code to render the newly created row.

So let’s now go back to our controller and write the method that will add the row on form submit.

def add_row
    @c = Component.create(params[:component])
end

On form submit, the add_row method is called and a new Component is created based on the items in the component hash.

Since we are calling this add_row, we can then just call our rjs template /views/inventory/add_row.rjs (just make sure you don’t have an rhtml/erb file in your /views/inventory folder). With the RJS file created, we can then fill it with:

page.insert_html :bottom, "components", {:partial=> "bike_component", :locals=>{:c=>@c}}
page.visual_effect :highlight, "component-#{@c.id}", :duration => 2.5
page.form.reset "component-form"

Notice in the add_row method in our controller that we assigned the newly created component the instance variable @c? Well, now we can access that from our RJS template and pass @c into the partial as the local variable :c. This way the partial can access the newly component object without any problems.

Now let’s see what this JavaScript in ruby syntax is doing. RJS takes advantage of prototype and script.aculo.us JavaScript libraries. In the above code, it is using:

*Insertion.Bottom to insert the given HTML in the bottom of the given DOM element “components”. In our case, we are rendering the newly created partial to the bottom of the components HTML table.
*Effect.Highlight scriptaculous effect to highlight the newly created component row.
*Prototypes Form.reset to reset the components form.

One last thing, since we are prototype and scriptaculous we have to include them in our application. To do this, we just go to our application layout and add the following in our < head >.

<%= javascript_include_tag :defaults %>

That’s it, we’re done! Go add all your bike parts to your new inventory app! The final result should look something like this screencast (with a tiny bit of css):

Further Reading

I highly recommend reading the O’Reilly RJS article to get a real grasp on what RJS can do for you.