Still fairly new to Rails. I used the acts_as_votable gem to create upvote and upvote buttons to allow users to upvote/downvote jokes, but I can't make them change from upvote / downvote (and vice versa) and update the counter each time they click without refreshing the page. I tried following other kind-of-similar answers without luck. Here's what I tried to implement.
Joke Model
acts_as_votable
User Model
acts_as voter
Routes.rb is
resources :jokes do
member do
get "like" => "jokes#upvote"
get "unlike" => "jokes#downvote"
end
end
Jokes Controller is
#upvote from user
def upvote
@joke = Joke.find(params[:id])
@joke.upvote_from current_user
respond_to do |format|
format.html { redirect_to :back }
format.js { render :layout => false }
end
end
#downvote from user
def downvote
@joke = Joke.find(params[:id])
@joke.downvote_from current_user
respond_to do |format|
format.html { redirect_to :back }
format.js { render :layout => false }
end
end
My view:
<div class="votes">
<%= link_to like_joke_path(@joke), method: :get, remote: true, class: 'like_joke' do %>
<button type="button" class="btn btn-info" aria-label="Left Align">
<i class="fa fa-arrow-up"></i>
<span class="badge vote_count"><%= @joke.get_upvotes.size %></span>
</button>
<% end %>
<%= link_to unlike_joke_path(@joke), method: :get, remote: true, class: 'unlike_joke' do %>
<button type="button" class="btn btn-info" aria-label="Left Align">
<i class="fa fa-arrow-down"></i>
<span class="badge voute_count"><%= @joke.get_downvotes.size %></span>
</button>
<% end %>
</div>
like.js.erb:
$('.like_joke').bind('ajax:success', function(){
$(this).parent().parent().find('.vote_count').html('<%= escape_javascript @joke.votes_for.size.to_s %>');
$(this).closest('.like_joke').hide();
$(this).closest('.votes').html(' <%= link_to "Unlike", unlike_joke_path(@joke), remote: true, method: :get, class: 'unlike_joke' %>');
});
and finally unlike.js.erb:
$('.unlike_joke').bind('ajax:success', function(){
$(this).parent().parent().find('.vote_count').html('<%= escape_javascript @joke.votes_for.size.to_s %>');
$(this).closest('.unlike_joke').hide();
$(this).closest('.votes').html(' <%= link_to "Like", like_joke_path(@joke), remote: true, method: :get, class: 'like_joke' %>');
});
Here's the rendered HTML:
<div class="votes">
<a class="like_joke" data-remote="true" data-method="get" href="/jokes/11-cat-joke-title/like">
<button type="button" class="btn btn-info" aria-label="Left Align">
<i class="fa fa-arrow-up"></i>
<span class="badge vote_count">0</span>
</button>
</a>
<a class="unlike_joke" data-remote="true" data-method="get" href="/jokes/11-cat-joke-title/unlike">
<button type="button" class="btn btn-info" aria-label="Left Align">
<i class="fa fa-arrow-down"></i>
<span class="badge voute_count">1</span>
</button>
</a></div>
When I look at the js console, I get an internal server error at jquery.js?body=1:9665 which is xhr.send( ( options.hasContent && options.data ) || null );
It seems like I'm close, but I think I've messed up something in my views and js. Can someone show me what I'm missing?
If you have a look at the code you've provided... the code is coloured for what is inside and outside of a string... and you'll notice this line here:
$(this).closest('.votes').html(' <%= link_to "Unlike", unlike_joke_path(@joke), remote: true, method: :get, class: 'unlike_joke' %>');
gets coloured so that parts of the code are inside the string... but parts aren't (scroll to the right to see it, it's near the end).
I don't know for sure that this is your error, but you probably should consider changing this to:
$(this).closest('.votes').html(' <%= link_to "Unlike", unlike_joke_path(@joke), remote: true, method: :get, class: "unlike_joke" %>');
if only to clear up ambiguity. (also check the other lines for similar issues)
Ok, I think the problem is this. once you have clicked like... you go to the action for liking (which, BTW you should probably call 'like' instead of 'upvote' for consistency)> Then you render the like.js.
at this point you want the js to actually update the like-count etc. You already have the javascript and you just want to run it right away. You don't need to bind it to ajax success... you already have succeeded. So your like.js should just contain the bare javascript to actually update the screen right now eg:
var like_joke = $('.unlike_joke');
like_joke.parent().parent().find('.vote_count').html('<%= escape_javascript @joke.votes_for.size.to_s %>');
like_joke.closest('.like_joke').hide();
like_joke.closest('.votes').html(' <%= link_to "Unlike", unlike_joke_path(@joke), remote: true, method: :get, class: 'unlike_joke' %>');
Note: untested and there may be bugs, but you get the idea. Don;'t put a function that will get called on ajax success or it will sit there waiting for some other success to happen. Success has already happened... now just go and do the thing :)
Also note: there's a typo here:
<span class="badge voute_count"><%= @joke.get_downvotes.size %></span>
should be
<span class="badge vote_count"><%= @joke.get_downvotes.size %></span>