Javascript with ruby on rails

Hello all!

I am new to Ruby on Rails and AJAX and I am curious as to the best way to achieve the following:

I have an index.html.erb file that lists some records.

In this page I have some checkbox buttons. When these buttons are checked or unchecked, a javascript array is updated.

var selected = [];

When my checkboxes are toggled a

filterchanged()

) javascript function is called that does the following ruby code to update the elements on the page (hides and shows elements without refreshing the page or using a controller

   <% Post.recent(**selected = []**).each do |post| %>
       ... retrieve data from database and hide/show elements
    <% end %>

I understand that since javascript runs on the client and ruby on the server, I can’t pass a js variable to ruby but how can I achieve the intended result?

Thank you.

You probably just want to return the html for the posts and replace them in the page.

e.g. make an ajax request to:
/posts/index?categories=1,2,3

PostsController


def index
  posts = Post.where('categories in (?)', params[:categories]);
  respond_to do |format|
    format.js
    format.html
  end
end

/posts/index.js.erb


$('#posts').html('<%= j render('posts/index') %>');

Using the js responses sends back a snippet of javascript along with the html to update.

Thank you for pointing me to the right direction!! I now have better understanding on the use of the controller.

I was trying to do everything in Javascript and hide show markers on a map using filters in the page and using different ruby scoped finders in the view which is not very MVC.

I will try to implement your suggestions and update the thread.

Since I have many filters in addition to the checkboxes I will have to pass multiple parameters in my ajax call, check for their presence, and then return the html.

What I don’t understand very well is the javascript code. If I understand correctly

$('#posts').html('<%= j render('posts/index') %>');

returns some javascript and html and it puts it in a #posts div. Is that correct? Can’t I just reload the post/index using the updated @posts from the controller?

Thank you for pointing me to the right direction!! I now have better understanding on the use of the controller.

No problemo.

I was trying to do everything in Javascript and hide show markers on a map using filters in the page and using different ruby scoped finders in the view which is not very MVC.

Definitely put all your queries in the controllers/models, but depending on the number of markers you may want to fetch everything up front and then toggle them client-side.
I’ve recently developed a maps application that queried based on lat/long bounds and appended new markers as you move around - but that may be overkill for you here.

Since I have many filters in addition to the checkboxes I will have to pass multiple parameters in my ajax call, check for their presence, and then return the html.

Yep, pass through all the params you need, make the queries to get the right posts and then return the HTML.

What I don’t understand very well is the javascript code. If I understand correctly

$('#posts').html('<%= j render('posts/index') %>');

returns some javascript and html and it puts it in a #posts div. Is that correct?

Yep.

Can’t I just reload the post/index using the updated @posts from the controller?

That’s kind of what the code does - It re-renders the ‘posts/index’ view and updates the #posts element with the contents.
“j” is a javascript escape helper to escape the quotes so it doesn’t break.

I’ll add a more complete implementation.

I think something like this should work.
I’m using the following gems in the example:

gem "coffee-rails"
gem "jquery-rails"
gem "decent_exposure"
# /controllers/posts_controller.rb
class PostsController < ApplicationController
  respond_to :html, :js
  
  expose(:posts) { posts_in_context }
  expose(:categories) { Category.all }
  
  def index
    respond_to do |format|
      format.html
      format.js
    end
  end
  
  protected
  
  def posts_in_context
    posts = Post.order_by("created_at DESC").limit(20)
    if params[:categories].present?
      posts = posts.where("category_id in (?)", params[:categories])
    end
    
    posts
  end
end


# /posts/index.erb
<h1>Posts</h1>
<ul id="categories">
  <%= form_tag posts_path, id: 'search-form' method: 'get', remote: true %>
    <% categories.each do |category| %>
      <li>
        <label><input name="categories[]" value="<%= category.id %>"><%= category.name %></label>
      </li>
    <% end %>
    <input type="submit" value="Search">
  <% end %>
</ul>
<ul id="posts">
 <%= render 'post_list' %>
</ul>


# /posts/index.js.erb
$('#posts').html('<%= j render('posts/post_list') %>');


# /posts/_post_list.erb
<% posts.each do |post| %>
  <li>
    <h2><%= post.title %></h2>
    <%= post.body %>
  </li>
<% end %>


# /assets/javascripts/posts.coffee
$('#categories').on 'change', 'input' (event)->
  $('#search-form').submit()
  
  false

I’ve got in the habit of using decent_exposure to expose methods to the views rather than using instance variables.
I’ve found it’s easier to share methods with various actions / views that way and it keeps things pretty clean.

Here’s a slightly different method returning straight HTML and putting all js on the client.
Note that remote: true on the form is removed in this example because we’re handling the ajax form submission ourselves.


# /controllers/posts_controller.rb
class PostsController < ApplicationController
  respond_to :html, :js
 
  expose(:posts) { posts_in_context }
  expose(:categories) { Category.all }
 
  def index
    respond_to do |format|
      format.html
      format.js { render 'post_list' }
    end
  end
 
  protected
 
  def posts_in_context
    posts = Post.order_by("created_at DESC").limit(20)
    if params[:categories].present?
      posts = posts.where("category_id in (?)", params[:categories])
    end
 
    posts
  end
end


# /posts/index.erb
<h1>Posts</h1>
<ul id="categories">
  <%= form_tag posts_path, id: 'search-form' method: 'get' %>
    <% categories.each do |category| %>
      <li>
        <label><input name="categories[]" value="<%= category.id %>"><%= category.name %></label>
      </li>
    <% end %>
    <input type="submit" value="Search">
  <% end %>
</ul>
<ul id="posts">
 <%= render 'post_list' %>
</ul>


# /posts/_post_list.erb
<% posts.each do |post| %>
  <li>
    <h2><%= post.title %></h2>
    <%= post.body %>
  </li>
<% end %>


# /assets/javascripts/posts.coffee
$('#categories').on 'change', 'input' (event)->
  $form = $('#search-form')
  $.ajax
    method: 'get'
    url: $form.attr('action') + '.js'
    success: (html)->
      $('#posts').html(html);
  
  false

I think I prefer the first example as it’s a little less code, requires less js on the client, but both have their place.

Thank you so much for your examples. Really useful!

Also the decent_exposure gem seem useful.

I will try to implement this and update. I have a different setup since I do not have a form and a submit button. I only have regular buttons with listeners on them.

The first example seems clearer indeed. Does it use ajax or does it refresh the page every time a button is changed?

If you want to use the code like in the first example you’ll need a form with remote: true
That just adds a data-remote=“true” attribute to the form which is picked up by the jquery_ujs library that is included with Rails by default.

It says you want to submit the form via ajax, these types of submissions get into the format.js {} block in respond_to