Как использовать форму для обновления списка моделей в представлении без обновления с помощью javascript (coffeescript) в rails?

По сути, я пытаюсь объединить пару представлений рельсов по умолчанию в один и использовать 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> по умолчанию.

Однако

  1. Это не обновление таблицы.
  2. Кажется, создается два экземпляра объекта.
  3. Он не отображает частичное (я также пробовал не частичное без начального подчеркивания), а вместо этого возвращает html для /books/<id> (но снова делает это дважды), как показано ниже.

Dynamicdisplay ... ... Перезагрузите страницу, чтобы получить исходный код: /assets/books.css?body=1 ... ... Книга успешно создана.

Название: Старик и море

Автор: Хемингуэй

Edit | Back Dynamicdisplay ... ... Book was successfully created.

Название: Старик и море

Автор: Хемингуэй

Edit | Back

Я думаю, что я близок, мне просто нужно получить javascript, чтобы получить правильный частичный и обновить DOM в нужном месте.

Спасибо за вашу помощь.


person hershey    schedule 13.08.2012    source источник


Ответы (1)


2: Я думаю, что вы создаете две книги, потому что вы запускаете отправку формы в своем CoffeeScript:

$('#add-button').click ->
  $(this).parents("form").submit()

и Rails делает то же самое автоматически для вас (поскольку вы установили свойство remote в форме, Rails внедрит скрипт на стороне клиента, необходимый для публикации содержимого формы с помощью Ajax). Таким образом, есть два запроса на каждое нажатие кнопки отправки.

3: Форма отправит запрос в действие create по умолчанию, поскольку вы передали новый объект в действие form_for, поэтому действие list никогда не вызывается запросом Ajax. Сгенерированное действие create сохраняет новый объект, а затем перенаправляет на действие show, поэтому вы видите возвращаемый html для /books/:id. Если вам нужно опубликовать форму для другого действия, вы можете сделать это, указав параметр url для form_for. Например:

<%= form_for @book, :remote => true, :html => { 'data-type' => :html }, :url => { :action => :list } do |f| %>

1: Это, вероятно, связано с 3. Запрос Ajax не возвращает строку таблицы, которую вы ожидаете. После того, как вы исправите 3, вы можете начать диагностировать проблему, чтобы увидеть, вызывается ли это событие:

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

Руководства по Rails, как правило, неплохое место для начала обучения, но руководство по Ajax выглядит немного устаревшим. Способ реализации Ajax в Rails может быть немного запутанным. Запросы Rails Ajax часто отображают шаблон js.erb для возврата JavaScript (CoffeeScript тоже работает), который запускается, как только ответ поступает в браузер. Таким образом, вам не нужно беспокоиться о подключении к событиям jQuery Ajax, поэтому вы можете рассмотреть этот подход. В учебнике по Rails есть пример.

person Steve    schedule 13.08.2012
comment
Спасибо за ответ. Вы были правы насчет отправки javascript. Добавление :url => { :action => :list } предотвращает создание нового объекта. Я предполагаю, что, поскольку он перенаправляется в список вместо создания, на самом деле ничего не создается. Когда я делаю alert data в функции coffeescript, я вижу html для представления шоу. - person hershey; 14.08.2012