Topic: [ AJAX Problem ] Dynamically Updating a List

Hi Railsform members,

I am trying to create a dynamically updating list but I am experiencing problems with a partial page.


When I submit my data the page only will render out the partial page.


Entering the Information:
http://img85.imageshack.us/img85/6087/listat8.th.jpg

The undesired result.
http://img135.imageshack.us/img135/7345/resultcj0.th.jpg

users_controller.rhtml

class UsersController < ApplicationController
  def index
    list
    render :action => 'list'
  end

  # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
  verify :method => :post, :only => [ :destroy, :create, :update ],
         :redirect_to => { :action => :list }

  def list
        @user_pages, @users = paginate :users, :per_page => 10
  end

  def show
    @user = User.find(params[:id])
  end
     
  def create
    @user = User.new(params[:user])
    if @user.save
      #flash[:notice] = 'User was successfully created.'
      render(:partial => "user" , :layout => false)
    end       
  end

  def edit
    @user = User.find(params[:id])
        render (:partial => 'form')
  end

  def update
    @user = User.find(params[:id])
    if @user.update_attributes(params[:user])
      flash[:notice] = 'User was successfully updated.'
      redirect_to :action => 'show', :id => @user
    else
      render :action => 'edit'
    end
  end

  def destroy
    User.find(params[:id]).destroy
    redirect_to :action => 'list'
  end
end


list.rhtml
<h2>User Listing</h2>
<%= error_messages_for 'user' %>
<table class="users" id="users">
  <tr>
    <th>Name</th>
        <th>Student #</th>
        <th>Phone</th>
        <th>Email</th>
        <th>Program</th>
        <th>Options</th>
  </tr>
    <tr class="add_user">
        <%= start_form_tag :action => :create %>
      <%= render :partial => 'form' %>
        <td>
            <%= form_remote_tag(:url => { :action => 'create' },:update => 'users' , :position => :top) %>
                <%= submit_tag("Add user" ) %>
            <%= end_form_tag %>
        </td>
        <%= end_form_tag %>
    </tr>
    <tr id="edit_user" class="edit_user"></tr>
    <%= render :partial => "user", :collection => @users %>
</table>

<%= link_to 'Previous page', { :page => @user_pages.current.previous } if @user_pages.current.previous %>
<%= link_to 'Next page', { :page => @user_pages.current.next } if @user_pages.current.next %>


_user.rhtml
    <%=['<tr id="edit_user_',user.id,'">'].compact%>
    <td><%= link_to [user.first_name, user.last_name].compact.join(" "), { :action => 'show', :id =>user}, :class =>'name' %></td>
        <td><%=h user.student_number %></td>
        <td><%=h user.phone %></td>
        <td><%=h user.email %></td>
        <td><%=h [user.program, user.year].compact.join(" ") %></td>
        <td>
        <%= link_to_remote ('Edit', :update => ['edit_user',user.id].compact.join("_"), :url => { :action => 'edit', :id => user })%>
        <%= link_to (image_tag("destroy.gif", :size => "16x16"), { :action => 'destroy', :id => user }, :confirm => 'Are you sure?', :post => true) %>
        </td>
    </tr>

http://localhost:3000/users/index generated
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>Users: index</title>
        <meta http-equiv="content-type" content="text/html;charset=utf-8" />
        <meta name="generator" content="NotePad++" />
        <link href="/stylesheets/basic.css?1165119994" media="screen" rel="Stylesheet" type="text/css" />
        <script src="/javascripts/prototype.js?1164353209" type="text/javascript"></script>

    </head>
    <body>
        <div class="container">
            <div class="header">
                <h1><span>Cage on Rails</span></h1>
                <div class="navigation">
                    <ul>
                        <li><a href="/users" class="active">Users</a></li>

                        <li><a href="/products">Products</a></li>
                        <li><a href="/orders">Orders</a></li>
                    </ul>
                </div><!-- navigation -->
            </div><!-- header -->
            <div class="content_wrap">
                <div class="content">
<p style="color: green"></p>

<h2>User Listing</h2>

<table class="users" id="users">
  <tr>
    <th>Name</th>
        <th>Student #</th>
        <th>Phone</th>
        <th>Email</th>

        <th>Program</th>
        <th>Options</th>
  </tr>
    <tr class="add_user">
        <form action="/users/create" method="post">
      <!--[form:user]-->
    <td>
        <input class="first_name" id="user_first_name" name="user[first_name]" size="30" type="text" />

        <input class="last_name" id="user_last_name" name="user[last_name]" size="30" type="text" />
    </td>
    <td><input class="student_number" id="user_student_number" name="user[student_number]" size="30" type="text" /></td>
    <td><input class="phone" id="user_phone" name="user[phone]" size="30" type="text" /></td>
    <td><input class="email" id="user_email" name="user[email]" size="30" type="text" /></td>
    <td>
        <input class="program" id="user_program" name="user[program]" size="30" type="text" />
        <input class="year" id="user_year" name="user[year]" size="30" type="text" />
    </td>

<!--[eoform:user]-->



        <td>
            <form action="/users/create" method="post" onsubmit="new Ajax.Updater('users', '/users/create', {asynchronous:true, evalScripts:true, insertion:Insertion.Top, parameters:Form.serialize(this)}); return false;">
                <input name="commit" type="submit" value="Add user" />
            </form>
        </td>
        </form>
    </tr>

    <tr id="edit_user" class="edit_user"></tr>
        <tr id="edit_user_1">
    <td><a href="/users/show/1" class="name">Andrew Brown</a></td>
        <td>100062999</td>
        <td>6269009</td>
        <td>abrown4</td>
        <td>Multimedia Production 2</td>

        <td>
        <a href="#" onclick="new Ajax.Updater('edit_user_1', '/users/edit/1', {asynchronous:true, evalScripts:true}); return false;">Edit</a>
        <a href="/users/destroy/1" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form'); this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit(); };return false;"><img alt="Destroy" height="16" src="/images/destroy.gif?1164359277" width="16" /></a>
        </td>
    </tr>    <tr id="edit_user_2">
    <td><a href="/users/show/2" class="name">Nick Martel</a></td>
        <td>100056777</td>

        <td>4722452</td>
        <td>nmartel</td>
        <td>Multimedia Production 3</td>
        <td>
        <a href="#" onclick="new Ajax.Updater('edit_user_2', '/users/edit/2', {asynchronous:true, evalScripts:true}); return false;">Edit</a>
        <a href="/users/destroy/2" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form'); this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit(); };return false;"><img alt="Destroy" height="16" src="/images/destroy.gif?1164359277" width="16" /></a>
        </td>

    </tr>    <tr id="edit_user_3">
    <td><a href="/users/show/3" class="name">Harriet Carlon</a></td>
        <td>100078333</td>
        <td>3459534</td>
        <td>hcarlso</td>
        <td>Film Production 2</td>

        <td>
        <a href="#" onclick="new Ajax.Updater('edit_user_3', '/users/edit/3', {asynchronous:true, evalScripts:true}); return false;">Edit</a>
        <a href="/users/destroy/3" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form'); this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit(); };return false;"><img alt="Destroy" height="16" src="/images/destroy.gif?1164359277" width="16" /></a>
        </td>
    </tr>    <tr id="edit_user_4">
    <td><a href="/users/show/4" class="name">Jim Voth</a></td>
        <td>210993333</td>

        <td>7672658</td>
        <td>jvoth</td>
        <td>Hooked on Phonetics 1</td>
        <td>
        <a href="#" onclick="new Ajax.Updater('edit_user_4', '/users/edit/4', {asynchronous:true, evalScripts:true}); return false;">Edit</a>
        <a href="/users/destroy/4" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form'); this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit(); };return false;"><img alt="Destroy" height="16" src="/images/destroy.gif?1164359277" width="16" /></a>
        </td>

    </tr>    <tr id="edit_user_5">
    <td><a href="/users/show/5" class="name">Tek Janson</a></td>
        <td>924573921</td>
        <td>3432414</td>
        <td>tekjanson</td>
        <td>Galatic Federation 9</td>

        <td>
        <a href="#" onclick="new Ajax.Updater('edit_user_5', '/users/edit/5', {asynchronous:true, evalScripts:true}); return false;">Edit</a>
        <a href="/users/destroy/5" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form'); this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit(); };return false;"><img alt="Destroy" height="16" src="/images/destroy.gif?1164359277" width="16" /></a>
        </td>
    </tr>    <tr id="edit_user_7">
    <td><a href="/users/show/7" class="name">Dream Rockster</a></td>
        <td>2147483647</td>

        <td>3452424</td>
        <td>drocks</td>
        <td>MM 1</td>
        <td>
        <a href="#" onclick="new Ajax.Updater('edit_user_7', '/users/edit/7', {asynchronous:true, evalScripts:true}); return false;">Edit</a>
        <a href="/users/destroy/7" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form'); this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit(); };return false;"><img alt="Destroy" height="16" src="/images/destroy.gif?1164359277" width="16" /></a>
        </td>

    </tr>    <tr id="edit_user_15">
    <td><a href="/users/show/15" class="name">Randy Marsh</a></td>
        <td>100056775</td>
        <td>3452637</td>
        <td>rmarsh</td>
        <td>Film Production 2</td>

        <td>
        <a href="#" onclick="new Ajax.Updater('edit_user_15', '/users/edit/15', {asynchronous:true, evalScripts:true}); return false;">Edit</a>
        <a href="/users/destroy/15" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form'); this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit(); };return false;"><img alt="Destroy" height="16" src="/images/destroy.gif?1164359277" width="16" /></a>
        </td>
    </tr>
</table>




                </div><!-- content -->
            </div><!-- content_wrap -->

            <div class="footer">
                <p>Cage Rules | Help | About </p>       
            </div><!-- footer -->
        </div>
    </body>
</html>


I have been knocking away at this problem for a few days and I haven't figured it out.
Any help would be appericated

Last edited by OmenKing (2006-12-03 00:44:32)

Re: [ AJAX Problem ] Dynamically Updating a List

The problem you have a nested form: an AJAX form inside a non AJAX form. HTML doesn't support nested forms, and therefore the top form is the one getting submitted. Try replacing the top form:

<%= start_form_tag :action => :create %>

With your remote form:

<%= form_remote_tag(:url => { :action => 'create' },:update => 'users' , :position => :top) %>

Railscasts - Free Ruby on Rails Screencasts

Re: [ AJAX Problem ] Dynamically Updating a List

class UsersController < ApplicationController
  def index
    list
    render :action => 'list'
  end

  # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
  verify :method => :post, :only => [ :destroy, :create, :update ],
         :redirect_to => { :action => :list }

  def list
        @user_pages, @users = paginate :users, :per_page => 10
  end

  def show
    @user = User.find(params[:id])
  end
     
  def create
    @user = User.new(params[:user])
    if @user.save
      flash[:notice] = 'User was successfully created.'
      render(:partial => "user" , :layout => false)
    end       
  end

  def edit
    @user = User.find(params[:id])
        render (:partial => 'form')
  end

  def update
    @user = User.find(params[:id])
    if @user.update_attributes(params[:user])
      flash[:notice] = 'User was successfully updated.'
      redirect_to :action => 'show', :id => @user
    else
      render :action => 'edit'
    end
  end

  def destroy
    User.find(params[:id]).destroy
    redirect_to :action => 'list'
  end
end


<h2>User Listing</h2>
<%= error_messages_for 'user' %>
<table class="users" id="users">
  <tr>
    <th>Name</th>
        <th>Student #</th>
        <th>Phone</th>
        <th>Email</th>
        <th>Program</th>
        <th>Options</th>
  </tr>
    <tr class="add_user">
        <%= form_remote_tag(:url => { :action => :create },:update => 'users' , :position => :top) %>
        <%= render :partial => 'form' %>
        <td><%= submit_tag("Add user" ) %></td>
        <%= end_form_tag %>
    </tr>
    <tr id="edit_user" class="edit_user"></tr>
    <%= render :partial => "user", :collection => @users %>
</table>

<%= link_to 'Previous page', { :page => @user_pages.current.previous } if @user_pages.current.previous %>
<%= link_to 'Next page', { :page => @user_pages.current.next } if @user_pages.current.next %>


http://img423.imageshack.us/img423/5944/resultrk8.th.gif

It almost works...
I am not sure why it doesn't

Last edited by OmenKing (2006-12-04 11:27:26)

Re: [ AJAX Problem ] Dynamically Updating a List

The user is failing validation upon saving, so the render call is skipped and it then tries to render create.rhtml (which obviously doesn't exist). For a simple fix, move the render call outside of the save condition.

def create
    @user = User.new(params[:user])
    if @user.save
      flash[:notice] = 'User was successfully created.'
    end
    render(:partial => "user" , :layout => false)
  end

I'm sure you will want to handle the errors better. You may want to look into AJAX for this as it will allow you to update multiple html elements at the same time. This way you could have a separate "errors" div which is updated upon error, instead of updating the entire user row.

Railscasts - Free Ruby on Rails Screencasts