Ask your JavaScript questions! Pay money and get answers fast! (more info)

How to use a form to update a list in a view without refresh JavaScript

A friend of mine asked this on [[LINK href="http://stackoverflow.com/questions/11927882/how-to-use-a-form-to-update-the-models-list-in-a-view-without-refresh-using-java"]]StackOverflow [[/LINK]] but they didn't get an answer. It is a complicated bit of Javascript and Coffeescript and Rails, but I think the fundamental question here is about Javascript.

--------------------------------


Fundamentally, I'm trying to combine a couple of default rails views into one and use javascript (actually coffeescript) to avoid having to do a page reload. Basically I want to combine what comes in the standard generated new and index functions so that when a new object is created it updates the list without having a page refresh.

I imagine this is a common technique in many applications although I haven't found a tutorial on it. I've tried stitching together a solution from help here at StackOverflow and elsewhere but haven't quite gotten it all to work. I decided to created a simple rails project both because I need help figuring it out and so others can follow along without getting bogged down in things specific to my application.

Below is the code. There's a lot but most of it is standard and generated by rails. I created a rails project (rails 3.2) and generated scaffolding (model, controller, view) for a Book that has two strings, a title and an author. I then further modified the code as below.

I made sure the Gemfile contains jquery-rails

gem 'rails', '3.2.7'

....

group :assets do
gem 'sass-rails', '~> 3.2.3'
gem 'coffee-rails', '~> 3.2.1'
gem 'uglifier', '>= 1.0.3'
end

gem 'jquery-rails'


Then I added a new route for the list action in Routes.rb

...
match 'books/list' => 'books#list'

resources :books

Following by adding the action to the controller books_controller.rb

...
def list
@all_books = Book.all
@book = Book.new

respond_to do |format|
format.html # new.html.erb
format.js { render 'new_book_row', :format => :html, :layout => false }
format.json { render json: @book }
end
end


I make sure to have both @book to hold the new book and @all_books to be used for the table listing all books.

The format.js line should render the new table row (file given below) as html (setting layout to false so it only renders the partial and not the application view wrappers).

We create the corresponding views and partials in views/books/

list.html.erb contains

<h1>List Books</h1>

<h3>Add Book</h3>
<%= render 'new_book_form' %>

<h3>My Books</h3>
<%= render 'book_list' %>

<%= link_to 'Back', books_path %>


I've split it into two partials although obviously the partials could be folded into the view above.

The _new_book_form.html.erb partial is as below

<%= form_for @book, :remote => true, :html => { 'data-type' => :html } do |f| %>
<% if @book.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@book.errors.count, "error") %> prohibited this book from being saved:</h2>

<ul>
<% @book.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>

<div class="field">
<%= f.label :title %><br />
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :author %><br />
<%= f.text_field :author %>
</div>
<div class="add-button" id='add-button'>
<%= f.submit %>
</div>
<% end %>


It's all standard generated code except the form_for call. I set :remote => true as was described at http://tech.thereq.com/post/18910961502/rails-3-using-form-remote-true-with-jquery-ujs to make it an ajax call. I needed to set 'data-type' => :html so the response came as html and not script.

The _book_list.html.erb partial is also all standard generated code

<table id='book-list'>
<tr>
<th>Title</th>
<th>Author</th>
<th></th>
<th></th>
<th></th>
</tr>

<% @all_books.each do |book| %>
<tr>
<td><%= book.title %></td>
<td><%= book.author %></td>
<td><%= link_to 'Show', book %></td>
<td><%= link_to 'Edit', edit_book_path(book) %></td>
<td><%= link_to 'Destroy', book, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</table>


and _new_book_row.html.erb is also straightforward

<tr>
<td><%= new_book.title %></td>
<td><%= new_book.author %></td>
<td><%= link_to 'Show', new_book %></td>
<td><%= link_to 'Edit', edit_book_path(new_book) %></td>
<td><%= link_to 'Destroy', new_book, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>


The magic is in books.js.coffee

# don't load javascript until the whole page is laid out
$(document).ready ->

# To save the book without reloading the page
$('#add-button').click ->
$(this).parents("form").submit()


$('form#new_book').on 'ajax:success', (xhr, data, status) ->
$('#book-list').append(data).show()


I found I needed to delay adding loading the javascript until after the page is loaded otherwise it wasn't finding the elements (no surprising when you think about it).

/books/list displays the page correctly and the button does create a new instance of the Book model with the form fields without rendering the default /books/<id> page.

However

It's not refreshing the table.

It seems to be creating two instances of the object.

It's not rendering the partial (I also tried is as a non partial without the leading underscore) and instead is returning the html for /books/<id> (but again doing it twice) as shown below.

Dynamicdisplay ... ... Reload the page to get source for: /assets/books.css?body=1 ... ... Book was successfully created.

Title: The Old Man and the Sea

Author: Hemingway

Edit | Back Dynamicdisplay ... ... Book was successfully created.
Title: The Old Man and the Sea

Author: Hemingway

Edit | Back

I think I'm close, I just need to get the javascript to get the right partial and update the DOM in the right place.

Thanks for your help.

Answers (1)

2012-08-17

Jarret Minkler answers:

Adding to a table is not so straight forward, try it as div's. You would actually have to traverse the array and append rows to the table dom etc.


Lawrence Krubner comments:

Thanks Jarret. I just sent your comment to my friend. I'm waiting for him to reply.


Jarret Minkler comments:

Are there any errors on the js? Hard to figure out without a live version to step through the js