SitePoint Sponsor

User Tag List

Results 1 to 8 of 8
  1. #1
    SitePoint Member
    Join Date
    Apr 2013
    Posts
    4
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Red face 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.

    Code:
    var selected = [];

    When my checkboxes are toggled a
    Code:
    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

    Code:
       <% 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.

  2. #2
    padawan silver trophybronze trophy markbrown4's Avatar
    Join Date
    Jul 2006
    Location
    Victoria, Australia
    Posts
    4,108
    Mentioned
    28 Post(s)
    Tagged
    2 Thread(s)
    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
    Code ruby:
    def index
      posts = Post.where('categories in (?)', params[:categories]);
      respond_to do |format|
        format.js
        format.html
      end
    end
    /posts/index.js.erb
    Code javascript:
    $('#posts').html('<%= j render('posts/index') %>');
    Using the js responses sends back a snippet of javascript along with the html to update.

  3. #3
    SitePoint Member
    Join Date
    Apr 2013
    Posts
    4
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    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

    Code:
    $('#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?

  4. #4
    padawan silver trophybronze trophy markbrown4's Avatar
    Join Date
    Jul 2006
    Location
    Victoria, Australia
    Posts
    4,108
    Mentioned
    28 Post(s)
    Tagged
    2 Thread(s)
    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
    Code:
    $('#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.

  5. #5
    padawan silver trophybronze trophy markbrown4's Avatar
    Join Date
    Jul 2006
    Location
    Victoria, Australia
    Posts
    4,108
    Mentioned
    28 Post(s)
    Tagged
    2 Thread(s)
    I think something like this should work.
    I'm using the following gems in the example:
    Code ruby:
    gem "coffee-rails"
    gem "jquery-rails"
    gem "decent_exposure"
    Code ruby:
    # /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
    Code ruby:
    # /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>
    Code javascript:
    # /posts/index.js.erb
    $('#posts').html('<%= j render('posts/post_list') %>');
    Code ruby:
    # /posts/_post_list.erb
    <% posts.each do |post| %>
      <li>
        <h2><%= post.title %></h2>
        <%= post.body %>
      </li>
    <% end %>
    Code coffeescript:
    # /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.

  6. #6
    padawan silver trophybronze trophy markbrown4's Avatar
    Join Date
    Jul 2006
    Location
    Victoria, Australia
    Posts
    4,108
    Mentioned
    28 Post(s)
    Tagged
    2 Thread(s)
    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.
    Code ruby:
    # /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
    Code ruby:
    # /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>
    Code ruby:
    # /posts/_post_list.erb
    <% posts.each do |post| %>
      <li>
        <h2><%= post.title %></h2>
        <%= post.body %>
      </li>
    <% end %>
    Code coffeescript:
    # /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.

  7. #7
    SitePoint Member
    Join Date
    Apr 2013
    Posts
    4
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    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?

  8. #8
    padawan silver trophybronze trophy markbrown4's Avatar
    Join Date
    Jul 2006
    Location
    Victoria, Australia
    Posts
    4,108
    Mentioned
    28 Post(s)
    Tagged
    2 Thread(s)
    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.
    https://github.com/rails/jquery-ujs

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


Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •