1

Problem description:

I have a view with set of links:

<% @feeds.each do |f| %>                
  <div class="feed">
    <span class="feed_counts"> <%= f.display_counts %> </span>
    <%= link_to "refresh", { :controller => 'Feeds', :action => "refresh_feed", :feed_id => f.id}, :remote => true, :class => 'refresh_feed_link' %>
    </div>
<% end %>

Users click on the link and launch next controller method:

def refresh_feed
  @feed = Feed.find(params[:feed_id])
  @feed.parse
end

Now I want to change the content of the corresponding span-element to @feed.total_count value.

My attempts:

Well, as I know there is a two way's to do it without reloading whole the page:

Way 1:

I can include js in my layout:

<%= render :partial => 'shared/partial_js' %>

and use this code in the partial_js:

<script type="text/javascript">
  $().ready(function() {

    $('.refresh_feed_link').bind('click', function() {
      $(this).closest('.feed').find('span.feed_counts').text('woo');
    });
 });
</script>

In this case I have '$(this)' and I can find the corresponding 'span' element. But I don't have any possibility to get my @feed varible value.

Way 2:

I can add

respond_to do | format |  
  format.js {render :layout => false}  
end

to my controller and create refresh_feed.js.erb file. In this JS file I can use my variable as <% @feed.total_count %>, but I don't know which of my multiple links was clicked. In the other words the $(this) variable in this file will be (window) and I cannot find corresponding span-element.

Question:

Is there any way to get what I want ?
Thanks in advance.

1 Answer 1

1

There are lots of ways to do this. Using the wayds that you described, here's a simple solution for the "which link was clicked" problem: dom_id.

1) Make a partial: app/views/feeds/_feed.html.erb

<%= div_for feed do %>
  <span class="feed_counts"> <%= feed.display_counts %> </span>
  <%= link_to "refresh", { :controller => 'Feeds', :action => "refresh_feed", :feed_id => feed.id}, :remote => true, :class => 'refresh_feed_link' %>
<% end %>

2) In your view:

<%= render @feeds %>

3) In your refresh_feed.js.erb file:

$('<%= dom_id(@feed) %>').replaceWith('<%= escape_javascript( render @feed ) %>');

There's another way that I personally like better, but it will take a me a little while to write it up, so I'll leave this for you here while I write up the other way.


Second Way

Here's how I do it, using CoffeeScript and HAML because they're easier to type. You can just convert this to plain JS and ERB and it will work the same.

I would setup my routes like so:

resources :feeds do
  get "refresh_feed", :on => :member

Assuming you've got a "feed" partial, app/views/feeds/_feed.html.haml:

= div_for feed, :class => 'feed_widget' do
  %span.feed_counts= f.display_counts
  = link_to "Refresh", refresh_feed_path(f), :class => 'refresh_link'

In any view:

= render @feeds

// or, more explicit:

= render :partial => 'feed/feeds', :collection => @feeds, :as => :feed

Now, in app/assets/javascripts/feeds.js.coffee

# Global Scope for CoffeesScript, ignore for JS
root = exports ? this

# Check page if there are feed widgets on it and initialize a handler for each one.
jQuery ->
  if $('div.feed_widget').length
    $('div.feed_widget').each ->
      new FeedWidget $(this)

root.FeedWidget = (container) ->
  @container = container
  @refresh_link = @container.find('a.refresh_link')
  @feed_counts = @container.find('span.feed_counts')
  this.initialize()

root.FeedWidget.prototype =
  initialize: ->
    self = this
    @feed_counts.click (event) ->
      event.preventDefault()
      $.ajax
        type: 'GET'
        url: self.refresh_link.attr 'href'
        dataType: 'json'
        error: (xhr, status, error) ->
          console.log error
          console.log xhr
        success: (data, status, xhr) ->
          self.feed_counts.text data.feed_counts

Then in your controller:

def refresh_feed
  @feed = Feed.find(params[:id]) #assuming you have a resourceful route to this.
  @feed.parse
  respond_to do |format|
    format.js { render :json => { feed_counts: @feed.counts }, :status => :ok } # use whatever method gets you the feed count here.
  end
end

Now you are getting a JSON response that is just the feed count, and you have a single JS listener/handler widget that will automatically show up (and function) and place you render that partial. Cool huh?

Note, the above code is not tested since I don't have your app, so you'll have to refine it for your needs. But ask questions and I'll do my best to answer.

Good luck!

Sign up to request clarification or add additional context in comments.

9 Comments

Thanks for your reply. After 1) and 2) I get the error "undefined local variable or method 'f' for #<#<Class:0x3c2ac00>:0x4303608>", so I have changed '<%= render @feeds %>' to '<%= render :partial => "feed.html.erb", :locals => { :f => @feeds } %>'. In this case I get the error 'undefined method `model_name' for Array:Class' because @feeds is array of records.
I accidentally left your local variable as 'f', it should have been 'feed'. I changed it in my expanded answer.
If I understand correctly I don't need to use each-construction in my view (<% @feeds.each do |feed| %>). Is it right ? <%= render @feeds %> will call partial for each feed in the @feeds automatically ?
Yes, it will. Thank you very much for your excelent answer. I have known many new things from your reply. But I need a little time test all of them :)
Yeah, render @feeds is just short for render :partial => 'feed/feeds', :collection => @feeds, :as => feed. That and render @feed (singular) are just built-in rails shortcuts if you follow the standard naming convention. Glad I could help, have fun!
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.