【问题标题】:Render three different partials depending on button clicked根据单击的按钮渲染三个不同的部分
【发布时间】:2016-08-10 13:49:57
【问题描述】:

所以我有这样的布局:

主页/index.html.erb:

<div class="optionscontainer btn-group btn-group-justified">
   <%= link_to posts_path, class:"options btn btn-primary", remote: true do %>
      <i class="fa fa-book optionseach" aria-hidden="true"></i>All posts
   <% end %>
   <%= link_to stories_path, class:"options btn btn-primary", remote: true do %>
      <i class="fa fa-book optionseach" aria-hidden="true"></i>All stories
   <% end %>
</div>
   <div id="content" class="">
   </div>

在posts_controller.rb中

我有:

def index
    @posts = Post.all
    respond_to do |format|
      format.html #looks for views/books/index.html.erb
      format.js   #looks for views/books/index.js.erb
    end
  end

在 stories_controller.rb 中

我有:

def index
    @stories = Story.all
    respond_to do |format|
      format.html #looks for views/books/index.html.erb
      format.js   #looks for views/books/index.js.erb
    end
  end

在我看来/posts/index.js.erb

$("#content").html("<%= j (render 'posts') %>");

在我的观点/故事/index.js.erb

$("#content").html("<%= j (render 'stories') %>");

我在views/posts 中也有_posts.html.erb_stories_html.erb 中的_stories_html.erbviews/stories

当我点击帖子按钮时,我会呈现视图,但当我点击故事按钮时,会发生什么情况?

【问题讨论】:

  • 你想如何渲染它们?作为 AJAX 请求?你知道如何在控制器上创建动作吗?
  • 我是 Rails 和 Web 开发的新手,如果 AJAX 是最好的方法,请举个例子

标签: ruby-on-rails ruby ruby-on-rails-4


【解决方案1】:

好的,在进入解决方案之前,让我们了解request-response 循环。

当您搜索 http://stackoverflow.com 时,您会从 client(您的浏览器)向 StackOverflow(SO) server 发送请求。客户端和服务器通过HTTP 协议进行通信,如果用户请求服务器知道的内容,它会提供(发送)响应(html、css、js 文件)。浏览器知道如何显示从服务器接收到的 html 内容。每个浏览器都有自己的样式表(user-agent-stylesheet),它还应用从服务器发回的 html 页面中链接的 css 文件中的样式。请注意,这一切都同步发生,当server 正在处理客户端的请求时,浏览器选项卡是inactive,因为它正在等待服务器的响应。单击链接时会发生相同的过程。它向服务器创建一个新请求。

来自服务器的响应可以是HTMLJSONXML 等。您可能已经注意到,synchronous 通信并不是我们一直想要的。

如果我们发出一个新的synchronous 请求,浏览器会重新获取HTMLCSSJSimage 文件(不要进入缓存)。而且我们不想为每个请求更新整个页面。

通常,只有部分页面在请求后更新,它提供了良好的用户体验。

这是 Javascript 擅长的地方。它有能力异步向服务器发出请求(网页不会重新加载),还可以使用称为AJAX(Asynchronous Javascript XML) 的方式更新页面的某些部分。

典型的 AJAX 请求是这样的。您向服务器发出请求,但这次是异步的,服务器以XML 而不是HTML 响应,Javascript 解析XML 文档更新页面的一部分。尽管它现在称为 AJAX,但 JSON 用于跨服务交换信息。

所以,要发出 AJAX 请求,我们需要一个链接,当单击该链接时会发送一个 XMLHttpRequest(异步请求),服务器应以 JSONXML 响应,然后脚本应解析响应并更新DOM(Document Object Model)。在Vanilla JS(纯javascript)中发出AJAX请求很复杂,人们通常使用Jqueryajax方法来发出AJAX请求(更少的代码行)。请参阅http://api.jquery.com/jquery.ajax/ 了解更多信息。

但在rails 中,它更容易。我们可以使用UJS(Unobtrusive Javascript) 发出 AJAX 请求。让我们看看它的实际效果。

要使链接发送 AJAX 请求,您需要在 link_to 助手中设置 remote: true。这会在生成的 HTML 中添加一个data-remote=true

例如下面的erb

<%= link_to "All books", books_path, remote: true %>

生成html

<a data-remote="true" href="/books">All books</a>

好的。现在我们都准备好发出AJAX 请求了。修改你的代码为

<div style="margin-top:50px;" class="wrapper">
  <div class="optionscontainer btn-group btn-group-justified">

    <%= link_to posts_path, class:"options btn btn-primary", remote: true do %>
      <i class="fa fa-book optionseach" aria-hidden="true"></i>All posts
    <% end %>


   <%= link_to stories_path, class:"options btn btn-primary", remote: true do %>
      <i class="fa fa-rss optionseach" aria-hidden="true"></i>All stories
    <% end %>

   <%= link_to books_path, class:"options btn btn-primary", remote: true do %>
      <i class="fa fa-users optionseach" aria-hidden="true"></i>All books
    <% end %>
</div>

<div id="content">
  <!-- The content goes here -->
</div>

我假设您已经设置了控制器、模型和视图。同时在终端中执行rake routes 以查看您的应用程序的现有路由。您应该看到以下内容(顺序不重要)

Prefix  Verb   URI Pattern                Controller#Action
posts   GET    /posts(.:format)            posts#index
stories GET    /stories(.:format)          stories#index
books   GET    /books(.:format)            books#index

注意:这里format对应的返回格式可以是htmljsxmljson

posts_path 在指向posts#indexurl_helper 之一中,这意味着每当在rails 应用程序中向服务器发出请求时,它首先到达路由器并被分派到相应的controller routes.rb中指定的操作

在这种情况下,如果我们向http://localhost:3000/books 发出请求,该请求将发送到 books#index 操作。在操作中,您可以从database 获取数据并将响应发送给客户端。

由于我们对 AJAX 感兴趣并且我们指定了 remote:true,rails 将期望向客户端返回一个 JS 响应(即负责动态呈现内容的 script)。

我将解释如何处理 BooksController 的 AJAX 请求,您可以将相同的想法应用于其他控制器。(postsstories)。

class BooksController < ApplicationController

  def index
    @books = Book.all

    respond_to do |format|
      format.html #looks for views/books/index.html.erb
      format.js   #looks for views/books/index.js.erb
    end

  end

  #other actions
end

我们在这里所做的只是告诉控制器在客户端请求 JS 响应时呈现 index.js.erb 或在 HTML 响应的情况下呈现 index.html.erb。当我们没有指定要渲染的文件时,rails 如何知道渲染index.html.erbindex.js.erb?这就是 Rails 受欢迎的原因。Rails 遵循Convention Over Configuration

实际上,controlleraction 名称推断要渲染的模板。

下一步是利用@books 来更新#content div。在添加代码渲染所有书籍之前,我们需要一个模板来渲染对吗?这就是partials的用武之地。partials是可重复使用的view' and a partial in rails is prefixed with '_'. For example:_books.html.erbis a partial forbooks`。

创建部分app/views/books/_books.html.erb

<% @books.each do |book| %>
  <div class="book">
    #Display the fields
  </div>
<% end %>

现在创建app/views/books/index.js.erb 并添加以下内容:

$("#content").html("<%= j (render 'books') %>");

这个单行将部分_books.html.erb 渲染到#content div 中。等待。它是如何工作的?让我们把它分解成碎片。

&lt;%= %&gt; 中的任何内容都是 ruby​​ 代码。它被执行并且值被替换为&lt;%= %&gt;erb 模板引擎允许您在 javascript 中编写 ruby 代码。那么,这有什么作用呢?

<%= j (render 'books') %>

它将渲染books/_books.html.erb,从参数推断为render。它返回由_books.html.erb 生成的html。

j 是做什么的?它实际上是escape_javascript 方法的别名。用于对部分_books.html.erb返回的内容进行转义。

解释escaping html 的原因将使这个答案更长。我强烈建议您在this SO 线程中阅读 kikito 的答案(第三个)。

因此,我们将 html 从部分作为字符串(注意 &lt;%= %&gt; 周围的引号)传递给添加到 #content div 中的 html 方法。而已!

我建议您检查服务器日志并探索开发者工具中的 Network 选项卡,以深入了解 AJAX 的工作原理。

对其他控制器(PostsControllerStoriesController)执行相同操作。

希望这会有所帮助。

【讨论】:

  • 当我遇到一个小问题时,我有书籍、帖子等的视图,但这些按钮位于 index#home 页面上,并使用称为 index 和方法 home 的不同控制器。我已经更新了我的问题。
  • @GurmukhSingh 我不明白你的意思。 index#home 视图中有三个链接,每个链接都向不同的控制器发送请求。在我的回答中,您可以找到指向stories_pathbooks_pathposts_path 的链接。在我的回答中,我已经为books_controller 编写了要完成的步骤,以便books_path 的链接呈现js 响应。您可以为其他控制器重复相同的步骤(每个数据不同)。如果那不是您想要的,请评论所需的行为。
  • 所以基本上我试图在单独的视图中呈现视图。 Post and Books 和单独的脚手架让他们有自己的看法。我在另一个名为 index 的控制器中工作,因此我将上述逻辑应用于索引控制器,在其中为索引 app/views/index/home.js.erb 创建 .js 文件,然后放置 $("#content") .html("");在其中,然后对于帖子,我执行相同的操作 $("#content").html("");在同一个 home.js.erb 中。我希望这能让场景:?这样就不行了?
  • @GurmukhSingh 为什么要将所有脚本都放在index/home.js.erb 中?如果你真的想那样做,那就有问题了。在链接中,您需要传递有关单击了哪个按钮的其他信息,而不仅如此,您还需要 index.js.erb 中的条件来呈现特定的部分,这使得它过于复杂。使用我的解决方案,您将请求定向到另一个控制器,从而避免条件,从而减少代码。有意义吗?
  • 我编辑了我的问题,展示了我是如何实现你的解决方案的,但它不能完美地工作
【解决方案2】:

链接必须设置为remote: true,并且它们必须指向不同的操作(或相同但接收不同的参数)。

而这些动作必须响应 js 并呈现其 js 视图。

应该是这样的:

你的看法:

<div style="margin-top:50px;" class="wrapper">
  <div class="optionscontainer btn-group btn-group-justified">
    <%= link_to 'All Posts', posts_path, class: 'options btn btn-primary', method: :get, remote: true %>
  </div>
  <div id='placeholder'>
    <%= render 'all_posts' %>
  </div>
</div>

views/posts/index.js.erb

$('#placeholder').html("<%= j (render 'all_posts') %>");

【讨论】:

    【解决方案3】:

    因为您想通过单一方法 index#home 来实现这一点。然后你必须通过链接传递一些额外的信息作为

    <div style="margin-top:50px;" class="wrapper">
      <div class="optionscontainer btn-group btn-group-justified">
        <a style="background-color:#4183D7;" href="<%= path_to_index_home(request: :p) %>" class="options btn btn-primary" data-remote=true><i class="fa fa-book optionseach" aria-hidden="true"></i>All posts</a>
        <a style="background-color:#59ABE3;" href="<%= path_to_index_home(request: :s) %>" class="options btn btn-primary" data-remote=true><i class="fa fa-rss optionseach" aria-hidden="true"></i>all stories</a>
        <a style="background-color:#81CFE0;" href="<%= path_to_index_home(request: :b) %>" class="options btn btn-primary" data-remote=true><i class="fa fa-users optionseach" aria-hidden="true"></i>all books</a>
      </div>
      <div data-load-partial>
        <%= render 'name_of_your_default_partial' , locals: {collection: @collection} %>
      </div>
    </div>
    

    现在我们必须编辑我们的控制器。在控制器顶部添加这一行

    ALLOWED_REQUESTS = { p: 'Post' , s: 'Story' , b: 'Book' }
    

    将您的主页操作编辑为

    @request = params[:request].blank? ? 'Post' : ALLOWED_REQUEST[params[:request].to_sym]
    @collection = @request.constantize.all
    

    现在是时候渲染适当的局部了。如果你有不同的表结构并且你想用不同的 html 显示不同的字段,那么你可以创建三个不同的部分作为 _book.html.erb , _story.html.erb , _post.html.erb ,你可以在家里调用它.js.erb 文件

    $('[data-load-partial]').html('<%= j render partial: "#{@request.downcase}" , locals: { collection: @collection } %>');
    

    如果你想对这三种类型使用相同的部分,那么你可以这样做

    $('[data-load-partial]').html('<%= j render partial: "name_of_your_partial" , locals: { collection: @collection } %>');
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-10-18
      • 1970-01-01
      • 2022-08-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-03
      相关资源
      最近更新 更多