По сути, я пытаюсь объединить пару представлений рельсов по умолчанию в один и использовать javascript (фактически coffeescript), чтобы избежать перезагрузки страницы. По сути, я хочу объединить то, что входит в стандартные сгенерированные функции new и index, чтобы при создании нового объекта он обновлял список без обновления страницы.
Я предполагаю, что это распространенная техника во многих приложениях, хотя я не нашел по ней учебника. Я пытался собрать решение из справки здесь, в StackOverflow и в других местах, но не совсем все заработало. Я решил создать простой проект на рельсах, потому что мне нужна помощь, чтобы понять его, и чтобы другие могли следовать за мной, не увязая в вещах, специфичных для моего приложения.
Ниже приведен код. Их много, но большинство из них стандартные и генерируются рельсами. Я создал проект rails (rails 3.2) и сгенерировал строительные леса (модель, контроллер, представление) для книги, которая имеет две строки, название и автора. Затем я дополнительно изменил код, как показано ниже.
Я убедился, что Gemfile
содержит 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'
Затем я добавил новый маршрут для действия списка в Routes.rb
.
...
match 'books/list' => 'books#list'
resources :books
После добавления действия в контроллер 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
Я уверен, что у меня есть @book
для хранения новой книги и @all_books
для таблицы со списком всех книг.
Строка format.js
должна отображать новую строку таблицы (файл, указанный ниже) как html (устанавливая для макета значение false, чтобы он отображал только частичные, а не оболочки представления приложения).
Создаем соответствующие представления и партиалы в views/books/
list.html.erb
содержит
<h1>List Books</h1>
<h3>Add Book</h3>
<%= render 'new_book_form' %>
<h3>My Books</h3>
<%= render 'book_list' %>
<%= link_to 'Back', books_path %>
Я разделил его на две части, хотя очевидно, что части могут быть свернуты в представление выше.
Партиал _new_book_form.html.erb
показан ниже.
<%= 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 %>
Это весь стандартный сгенерированный код, кроме вызова form_for. Я установил :remote => true
, как описано в http://tech.thereq.com/post/18910961502/rails-3-using-form-remote-true-with-jquery-ujs, чтобы сделать это вызовом ajax. Мне нужно было установить 'data-type' => :html
, чтобы ответ пришел в виде html, а не скрипта.
Партиал _book_list.html.erb
также является стандартным сгенерированным кодом.
<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>
и _new_book_row.html.erb
тоже прямолинейный
<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>
Магия в 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()
Я обнаружил, что мне нужно отложить добавление загрузки javascript до тех пор, пока страница не будет загружена, иначе она не найдет элементы (неудивительно, если подумать).
/books/list
отображает страницу правильно, а кнопка создает новый экземпляр модели Book с полями формы без рендеринга страницы /books/<id>
по умолчанию.
Однако
- Это не обновление таблицы.
- Кажется, создается два экземпляра объекта.
- Он не отображает частичное (я также пробовал не частичное без начального подчеркивания), а вместо этого возвращает html для
/books/<id>
(но снова делает это дважды), как показано ниже.
Dynamicdisplay ... ... Перезагрузите страницу, чтобы получить исходный код: /assets/books.css?body=1 ... ... Книга успешно создана.
Название: Старик и море
Автор: Хемингуэй
Edit | Back Dynamicdisplay ... ... Book was successfully created.Название: Старик и море
Автор: Хемингуэй
Edit | BackЯ думаю, что я близок, мне просто нужно получить javascript, чтобы получить правильный частичный и обновить DOM в нужном месте.
Спасибо за вашу помощь.