Gurmukh Singh Gurmukh Singh - 3 months ago 11
Ruby Question

Render three different partials depending on button clicked

So I have a layout like this:

home/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>


In posts_controller.rb

I have:

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


In stories_controller.rb

I have:

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


In my views/posts/index.js.erb

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


In my views/stories/index.js.erb

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


And I also have
_posts.html.erb
in
views/posts
and
_stories_html.erb
in
views/stories


What happens is when i click on the posts button i renders the view but when i click on the stories button nothing renders?

Answer

Ok, before getting into the solution, lets understand the request-response cycle.

When you search for http://stackoverflow.com, what happens is you're sending a request from the client(your browser) to the StackOverflow(SO) server. The client and server communicate through HTTP protocol and if the user's requesting for something that the server knows, it serves(sends) the response(html, css, js files). The browser knows how to display the html content received from the server. Every browser has its own stylesheet(user-agent-stylesheet) and it also applies the styles in the css files linked in the html page sent back from the server. Note that this all happens synchronously and while the server is processing the client's request, the browser tab is inactive as its waiting for the server's response. The same process happens when you click on a link. It creates a new request to the server.

The response from the server can be HTML, JSON, XML etc. As you might have noticed, the synchronous communication is not something we always want.

If we make a new synchronous request, the browser fetches HTML, CSS and JS and image files all over again(lets not get into caching). And we don't want to update the whole page for every request.

Often, only parts of the page are updated after a request and it provides a good user experience.

This is where Javascript is good at. It has the power to make requests to the server asynchronously(the web page doesn't reload) and also update parts of the page using something called AJAX(Asynchronous Javascript XML).

A typical AJAX request goes like this. You make a request to the server but this time asynchronously and the server responds with XML rather than HTML and Javascript parses the XML document updates the part of the page. Eventhough its called AJAX, now-a-days, JSON is used for exchanging information across services.

So, to make a AJAX request, we need a link which when clicked sends a XMLHttpRequest(asynchronous request) and the server should respond with JSON or XML or and then the script should parse the response and update the DOM(Document Object Model). Making an AJAX request in Vanilla JS(plain javascript) is complex and people normally use Jquery's ajax method to issue an AJAX request(less lines of code). See http://api.jquery.com/jquery.ajax/ for more information.

But in rails, its even easier. We can make an AJAX request using UJS(Unobtrusive Javascript). Lets see it in action.

To make a link send an AJAX request, you need to set remote: truein the link_to helper. This adds a data-remote=truein the generated HTML.

For example the following erb

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

generates the html

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

Ok. Now we're all set to make an AJAX request. Modify your code as

<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">
  <!-- Our content goes here -->
</div>

I assume you have the controllers, models and views setup. Also Execute rake routes in the terminal to see the existing routes of your application. You should see the following(Order isn't important)

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

Note: format here corresponds to the returned format which can be html, js, xml or json.

posts_path in one of the url_helper which points to posts#index, meaning whenever a request is made to the server in a rails application, it first reaches the router and is dispatched to the corresponding controller action specified in routes.rb

In this case, if we make a request to http://localhost:3000/books, the request is sent to the books#index action. In the action, you can fetch the data from the database and send the response to the client.

Since we are interested in AJAX and we have specified remote:true, rails will expect a JS response to be returned to the client(ie. a script which is responsible for rendering content dynamically).

I will explain how to deal with the AJAX request for BooksController and you can apply the same idea for other controllers.(posts and stories).

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

All we're doing here is telling the controller to render index.js.erb if the client requests for a JS response or to render index.html.erb in case of HTML response. How does rails knows to render index.html.erb or index.js.erb when we didn't specify the file to render? Thats what rails is popular for.Rails follows Convention Over Configuration.

Actually, the controller infers the template to render from the action name.

The next step is to make use of @books to update the #content div. Before adding the code to render all the books, we need a template to render right? Thats where partials come in. A partial is a reusable view' and a partial in rails is prefixed with '_'. For example:_books.html.erbis a partial forbooks`.

Create a partial app/views/books/_books.html.erb

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

Now create app/views/books/index.js.erb and add the following:

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

This one-liner will render the partial _books.html.erb into the #content div. Wait. How does it work? Lets break it into pieces.

Anything inside <%= %> is ruby code. Its executed and the value is replaced in place of <%= %>. The erb templating engline allows you to write ruby code inside javascript. So, what does this do?

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

It will render the books/_books.html.erb, inferred from the parameter to render. It returns the html generated by _books.html.erb.

What does j do? Its actually an alias of escape_javascript method. It is used to escape the content returned from the partial _books.html.erb.

Explaining the reason for escaping html will make this answer even longer. I strongly recommend you to read kikito's answer(the 3rd one) in this SO thread.

So, we are passing html from the partial as a string(note the quotes around <%= %>) to html method which is added inside the #content div. Thats it!

I recommend you to inspect the server logs and explore the Network tab in Developer tools to get a deep understanding of how AJAX works.

Do the same for the other controllers(PostsController and StoriesController).

Hope this helps.

For more, read https://richonrails.com/articles/basic-ajax-in-ruby-on-rails