1

从根本上说,我试图将几个默认的 rails 视图合并为一个,并使用 javascript(实际上是 coffeescript)来避免重新加载页面。基本上我想结合标准生成的新函数和索引函数中的内容,以便在创建新对象时更新列表而无需刷新页面。

我想这是许多应用程序中的常用技术,尽管我还没有找到关于它的教程。我已经尝试从 StackOverflow 和其他地方的帮助中拼凑出一个解决方案,但还没有完全发挥作用。我决定创建一个简单的 Rails 项目,因为我需要帮助来解决这个问题,因此其他人可以跟进,而不会陷入特定于我的应用程序的事情中。

下面是代码。有很多,但大部分都是标准的,由 rails 生成。我创建了一个 rails 项目(rails 3.2)并为具有两个字符串、一个标题和一个作者的 Book 生成了脚手架(模型、控制器、视图)。然后我进一步修改了代码如下。

我确保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 调用之外,它都是标准生成的代码。我按照http://tech.thereq.com/post/18910961502/rails-3-using-form-remote-true-with-jquery-ujs:remote => true中的描述进行设置,使其成为 ajax 调用。我需要进行设置,以便响应以 html 而不是脚本的形式出现。'data-type' => :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 ... ... 图书已成功创建。

书名: 老人与海

作者: 海明威

编辑 | 返回 Dynamicdisplay ... ... 图书创建成功。

书名: 老人与海

作者: 海明威

编辑 | 后退

我想我已经接近了,我只需要获取 javascript 来获取正确的部分并在正确的位置更新 DOM。

谢谢你的帮助。

4

1 回答 1

2

2:我认为您正在创建两本书,因为您在 CoffeeScript 中触发了表单提交:

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

Rails 会自动为您做同样的事情(因为您已经在表单上设置了远程属性,Rails 将注入通过 Ajax 发布表单内容所需的客户端脚本)。所以每次提交按钮点击有两个请求。

3:默认情况下,表单会将请求发布到create操作,因为您已将新对象传递给,form_for因此listAjax 请求永远不会调用该操作。生成的create操作会保存新对象,然后重定向到该show操作,这就是您看到 /books/:id 的 html 被返回的原因。如果您需要将表单发布到其他操作,则可以通过将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 指南看起来有点过时了。Rails 执行 Ajax 的方式可能有点令人困惑。Rails Ajax 请求通常会渲染一个 js.erb 模板以返回 JavaScript(CoffeeScript 也可以),一旦响应到达浏览器就会运行。这样您就不必担心挂钩到 jQuery Ajax 事件,因此您可能需要考虑这种方法。Rails 教程有一个例子

于 2012-08-13T19:35:26.720 回答