I'm facing a weird bug with my ajax, where the like button is activating for all post in the view. I never had this issue with the first iteration of this feature. Its a simple two state like button that hides the filled heart with the empty heart if the current user isn't liking the post. The code that I have is written below. For the buttons themselves, I'm using a simple css class/png background url.
stories_controller.rb
def like
#story = Story.friendly.find(params[:id])
if !current_user.liked? #story
#story.liked_by current_user
elsif current_user.liked? #story
#story.unliked_by current_user
end
respond_to do |format|
format.js {render :nothing => true }
end
end
stories\like.js.erb
<% if current_user.liked? #story %>
$('#like-story-id').removeClass('story-heart-btn').addClass('story-heart-btn-active');
<% else %>
$('#like-story-id').addClass('story-heart-btn-inactive');
<% end %>
$('.likes-story-count').html("<%= #story.get_upvotes.size %>");
_story.html.erb
<ul id="left-story-footer-list">
<li class="story-votes" id="#story_<%= story.id %>">
<%= link_to like_story_path(story), style: 'text-decoration: none', class: 'like-story-btn', method: :put, remote: true do %>
<div class="story-heart-btn" id="like-story-id"></div>
<% end %>
</li>
<li><p class="story-card-text-format likes-story-count"><%= number_with_delimiter(story.get_likes.size) %></p></li>
</ul>
routes.rb
resources :stories do
member do
put 'like', to: 'stories#like'
get 'like', to: 'stories#like'
end
end
Doesn't this like-story-id should also be a unique inside ul?
<div class="story-heart-btn" id="like-story-id"></div>
Related
My application has a Micropost model and a Comment model.
I introduced in the Micropost partial links for showing/hiding and refreshing comments:
<li id="micropost-<%= micropost.id %>">
<span class="user"><%= link_to micropost.user.name, micropost.user %></span>
<span class="content">
<%= micropost.content %>
<%= image_tag micropost.picture.url if micropost.picture? %>
</span>
<% if micropost.comments.any? %>
~ <%= link_to "Show/hide comments", "#", class: "comments-link", remote: true %>
~ <%= link_to "Refresh comments", fetch_comments_path(micropost), remote: true %>
<% end %>
<% if logged_in? && (current_user == micropost.user || current_user.friend?(micropost.user)) %>
<div class="comment-section">
<%= form_for(current_user.comments.build, remote: true) do |f| %>
...
<% end %>
</div>
<% end %>
<div class="comments-section">
<% if micropost.comments.any? %>
<ol id="comments_micropost-<%= micropost.id %>">
<% micropost.comments.each do |comment| %>
<%= render comment %>
<% end %>
</ol>
<% end %>
</div>
</li>
This links are added to the page after creating the first comment and are loaded if the micropost has comments.
However, in case there is just one comment and it is deleted, they are useless so I would like to remove them with Ajax via the destroy action of the comments controller.
I am having my troubles to realize this.
The main problem is that I cannot refer to the micropost using the to-be-destroyed comment.
Once the comment is destroyed any #micropost = #comment.micropost association would return a nil object:
def destroy
#comment = current_user.comments.find_by(id: params[:id])
#micropost = #comment.micropost
#comment.destroy
respond_to do |format|
format.html do
flash[:success] = "Comment deleted"
redirect_to request.referrer || root_url
end
format.js
end
end
Thus, inside create.js.erb I cannot use a if #micropost.comments.nil? remove links conditional
Moving #comment.destroy after the respond_to block would not work for format.html, without refreshing the page.
If it worked, I could use a if #micropost.comments.count == 1 remove links conditional in create.js.erb
I cannot move #comment.destroy inside the format.html block, because it would not be used by format.js.
What solution can I use?
The delete link in the comment partial is:
<%= link_to "delete", comment, method: :delete, remote: true %>
Is it possible to pass in link_to a params value equal to micropost.id, such as:
<%= link_to "delete", comment, method: :delete, micropost_id: micropost.id, remote: true %>
so that I can write in the destroy action:
#micropost = Micropost.find(params[:micropost_id]) ?
When you destroy your object it is destroyed form database and all reference keys are destroyed immediately. So you can first grab post Id and the remove comment.
def destroy
#micropost_id = #comment.micropost.id
#comment.destroy
end
Now you can use #micropost_id to take another action or to redirect to the parent post like this:
redirect_to '/microposts/'+ #micropost_id
Or
redirect_to MicroPost.find(micropost_id)
I realise that there are several similar questions, but none of them seem to address the issue with nested resources. I'm quite new to ROR and I am trying to implement a like/unlike using ajax with the acts_as_votable gem. I kind of understand how ajax works in Rails but I seem to have problems implementing it on my own.
The structure
My app has a list of Qs which each has a list of As. Users can 'bookmark/un-bookmark' a question or 'like/unlike' an answer. 'Downvoting' an answer like in stackoverflow is not permitted - users may only 'undo' their cast votes.
Current Attempts
This is the like snippet of my show.html.erb for a Question. It's not a separate partial file. It's within the show view.:
<% #question.answers.each do |answer| %>
*omit*
<% if current_user.id!=answer.user_id %>
<div class="heart-like">
<br>
<!-- Like Button -->
<% if current_user.liked? answer %>
<%= link_to unvote_question_answer_path(#question, answer), id: “like_#{answer.id}”, remote: :true do %>
<span class="fa fa-heart fa-2x"></span>
<% end %>
<% else %>
<%= link_to like_question_answer_path(#question, answer), id: “like_#{answer.id}”, remote: :true do %>
<span class="fa fa-heart-o fa-2x"></span>
<% end %>
<% end %>
<br>
</div>
<% end %>
My routes:
resources :questions do
member do
get "bookmark"
end
resources :answers do
member do
put "like", to: "answers#upvote"
put "unvote", to: "answers#unvote"
end
resources :comments, only: [:index, :new, :create, :destroy]
end
end
My answers controller has these two methods:
def upvote
begin
#question = Question.find(params[:question_id])
#answer = Answer.find(params[:id])
#answer.liked_by current_user
respond_to do |format|
format.html { redirect_to :back }
format.js
end
rescue ActiveRecord::RecordInvalid
render :nothing => true, :status => 404
end
end
# undo prev vote
def unvote
begin
#question = Question.find(params[:question_id])
#answer = Answer.find(params[:id])
#answer.unliked_by current_user
respond_to do |format|
format.html { redirect_to :back }
format.js
end
end
end
And this is the js I made with tremendous help from this blog, which I don't know where to put:
$(“#like_<%= #answer.id %>”).removeClass(‘fa fa-heart-o’).addClass(‘fa fa-heart’);
$(“#like_<%= #answer.id %>”).attr(“href”, ‘/answers/<%= #answer.id %>/unlike’);
$(“#likes_<%= #answer.id %>”).html(“<%= j (render partial: ‘answers/likes’, locals: {answer: #answer} ) %>”);
=> BUT I think everything should be #question.answer.id, because the answers are nested within the questions -> which I am completely confused over. But if I use #question.answer.id, what should {answer: #answer} this part look like?
And the partial is (_likes.html.erb)
<div class="num-votes">
<%= answer.votes_for.size %> Likes
</div>
So I'm confused about a few things:
Where does _likes.html.erb and like.js.erb go in my project dir (in answers? in questions?)
How do I write my like.js.erb file when the answers are nested (made a few attempts but all failed :( )
And how can I get this to work? (currently the like doesn't do anything... and the heart isn't displayed as well.)
Thanks in advance.
I believe you are not getting the right id when iterating, instead always using #answer.id on javascript file.
This might help a little:
Onclick/Javascript for div; Rails iterator
Your js should be in a template like an html.erb but js.erb. So this js
$("#like_<%= #answer.id %>").removeClass('fa fa-heart-o').addClass('fa fa-heart');
$("#like_<%= #answer.id %>").attr("href", '/answers/<%= #answer.id %>/unlike');
$("#likes_<%= #answer.id %>").html("<%= j (render partial: 'answers/likes', locals: {answer: #answer} ) %>");
Should be in "views/answers/upvote.js.erb" but I'm not sure but if you update your post with your routes with command 'rails routes', you'll have a response
I have been searching thru answers here for my problem but cant seem to find the exact fit. I am working on a blog and on each post you can vote if you like or dislike them. I am using act_as_votable for my voting table. The problem I am having is once you click on like or dislike you are redirected to the top of the page which gets annoying since you want to continue from where you were and not need to start at the top. I have been told using ajax is too complicated due to how new I am so i wanted to use redirect_to and anchor and just make the ID the story that was liked or disliked. Long story short, after reading every stackoverflow on this topic I still have no idea how to do it. I know I am close but think I need some help.
AFter more searching I have tried to convert from an anchor to using ajax so the page does not need to reload everytime someone clicks like or dislike. I have read many stackoverflows that are similar to mine, How to add an anchor to redirect_to :back, this one seems to be most simliar however i am still having problems.This is what I have:
Stories Controller
def upvote
#story = Story.find(params[:id])
#story.upvote_by(current_user)
respond_to do |format|
format.html {redirect_to :back}
format.js { render layout: false }
end
end
def downvote
#story = Story.find(params[:id])
#story.downvote_by(current_user)
respond_to do |format|
format.html { redirect_to :back }
format.js { render layout: false }
end
end
_story.html.erb
<div class="story_partial" id="story_<%= story.id %>">
<%= story.content %>
<div id="downvote_button_<%= story.id %>">
<%= link_to "dislike", downvote_path, method: "post", remote: true %>
</div>
</div>
<div class="story_partial" id="story_<%= story.id %>">
<%= story.content %>
<div id="upvote_button_<%= story.id %>">
<%= link_to "like", upvote_path, method: "post", remote: true %>
</div>
</div>
upvote.js.erb
$('#story_<%= #story.id %>').html('<%= j render #story %>');
index.html.erb
<%= link_to like_story_path(story), method: :post, remote: true, class: "btn btn-warning btn-xs" do %>
That scared me! <span class="text-warning"> </span> <% end %>(<%= story.get_upvotes.size %>)
#I know these two buttons are different. I was trying to find the best way to make them work but neither automatically update. I need to refresh the page to have the counters update
<%= button_to dislike_story_path(story), method: :get, remote: true, class: "btn btn-success btn-xs" do %>
You Wimp! <span class="text-warning"></span><% end %>
(<%= story.get_downvotes.size %>)
routes.rb
resources :stories do
member do
post :like, to:'stories#upvote'
get :dislike, to:'stories#downvote'
end
end
The buttons do work once I refresh the page however I am looking to get the like/dislike button to auto update once you click either like/dislike
Any help is appreciated.
Thank you
Here's syntax for the link you should need :
<%= link_to "Blahblahblah this is a link", story_path(#story) + "#story_#{#story.id}" %>
I have a rails app, which is constructed by three parts such as navigation above, main content, and side menu.
I already implemented follow un-follow button in my main content.
It's working with Ajax perfectly so far.
basically if I press follow button, follow action in users_controller.rb will be called and changes follow flag, and then it calls follow.js to re-render follow button as partial.
Beside that, in side menu, it has the number of people whom current_user is following.
This number also should be refreshed right after follow action was executed.
To refresh more than 2 partials at once. How can I archive this?
controllers/users_controller.rb
....
def follow
#user = User.find(params[:id])
current_user.follow(#user)
respond_to do |format|
format.js {render :action=>"follow.js"}
end
end
def unfollow
#user = User.find(params[:id])
current_user.stop_following(#user)
respond_to do |format|
format.js {render :action=>"unfollow.js"}
end
end
...
views/users/follow.js.erb
$('.follow_user[data-user-id="<%=#user.id%>"]').html('<%= escape_javascript(render :partial => "users/follow_user", :locals => {:user => #user}) %>');
views/users/unfollow.js.erb
$('.follow_user[data-user-id="<%=#user.id%>"]').html('<%= escape_javascript(render :partial => "users/follow_user", :locals => {:user => #user}) %>');
views/layouts/_menu.html.erb
<% if user_signed_in? %>
<li><%= link_to(following_user_path(current_user.username)) do %>
<i class="icon-heart"></i>Following
<%= '(' + current_user.all_following.count.to_s + ')' if current_user.all_following.count > 0 %>
<% end %>
</li>
<li><%= link_to(followed_user_path(current_user.username)) do %>
<i class="icon-heart"></i>Followed by
<%= '(' + current_user.followers.count.to_s + ')' if current_user.followers.count > 0 %>
<% end %>
</li>
<% else %>
<li><%= link_to sanitize('<i class="icon-heart"></i> ') + "Following", following_user_path %></li>
<li><%= link_to sanitize('<i class="icon-heart"></i> ') + "Followed by", followed_user_path %></li>
<% end %>
# dedicate partial _following_number.html.erb
<span id="following_number">
<% if user.followers.count > 0 %>
<%= '(' + user.followers.count.to_s + ')' %>
<% end %>
</span>
# update layout with partial:
<li><%= link_to(followed_user_path(current_user.username)) do %>
<i class="icon-heart"></i>Followed by
<%= render :partial => "following_number", :user => current_user %>
<% end %>
# follow.js.erb && unfollow.js.erb:
...
$(document).find("#following_number").replaceWith('<%= escape_javascript(render("following_number", :user => #user, :formats => [:html])) %>')
More elegant with helper:
def following_number(user)
user.followers.count > 0 ? "(#{user.followers.count})" : nil
end
# then put spans to layout:
<span id="following_number"><%= following_number(current_user) %></span>
# and js:
$(document).find("#following_number").html('<%= escape_javascript(following_number(#user)) %>')
You could create a new action that render_to_string both of those partials and format them into a json object:
{
div_id_1: "<ul....." ,
div_id_2: "<div...."
}
One simple way of doing this is to return a .js from rails and have your view execute this script, which will have the actions to update/replace your view.
For example this simply renders three partials and later using jQuery replaces the content in your view. Lets say the action is my_action so you will place the following code in your my_action.js.erb:
$("#content_1").html("<%= escape_javascript(render('partial_1'))%>");
$("#content_2").html("<%= escape_javascript(render('partial_2'))%>");
$("#content_3").html("<%= escape_javascript(render('partial_3'))%>");
This example can be extended the way you want or need. You can also render json for your models and call a function to update them in the view.
I have a nav menu with 2 tabs/links in the show.html.erb file, in UsersController.rb, I would like to use ajax to render different partial for the tabs.
In the show.html.erb I have a div named profile-data where I want to show the content.
So I do something like this:
The link structure:
<li><%= link_to "College friends", college_friends_path, :remote => true %></li>
<li><%= link_to "Highschool friends", highschool_friends_path, :remote => true %></li>
I define the routes:
match "college_friends" => "users#college_friends", :as => "college_friends"
match "highschool_friends" => "users#highschool_friends, :as => "highschool_friends"
And I define in my UserController.rb the necessary methods:
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
def college_friends
respond_to do |format|
format.js
end
end
def highschool_friends
respond_to do |format|
format.js
end
end
end
Last thing we have the JS files:
*college_friends.js.erb*
$('#profile-data').html("<%= escape_javascript(render(:partial => 'college_friends')) %>");
*highschool_friends.js.erb*
$('#profile-data').html("<%= escape_javascript(render(:partial => 'highschool_friends')) %>");
The partial code: _college_friends.html.erb
<% groups = #user.friends.group_by(&:college_name) %>
<% sorted_groups = groups.sort_by{|key, values| values.count}.reverse %>
<% sorted_groups.each do |collegename, friends| %>
<% next if collegename.blank? %>
<div class="contentbox">
<div class="box-header">
<h3><%= collegename %></h3>
<div class="meta-info">
<p><i class="icon-map-marker"></i> Malmö</p>
<p><i class="icon-user"></i><span class="count"> <%= friends.count %></span> vänner</p>
</div>
</div>
<ul class="friends-list">
<% friends.map do |friend| %>
<li><%= image_tag(friend.image) %>
<% end %>
</ul>
</div>
<% end %>
So I solve this problem but checking the source in and out. And I notice that the js files was not loaded in the app.
So I changed this:
<%= javascript_include_tag :defaults %>
To this:
<%= javascript_include_tag "application" %>
And it loaded all the necessary js files.
But then I get this error:
ActionView::Template::Error (undefined method `friends' for nil:NilClass):
You are getting
ActionView::Template::Error (undefined method `friends' for nil:NilClass):
Because
<% groups = #user.friends.group_by(&:college_name) %>
requires #user to be set.
As Brandon suggested, try
def college_friends
#user = User.find(params[:id])
respond_to do |format|
format.js
end
end
BUT
To achieve this, you need to change your link routes to include user id, similar to this
match "college_friends/:id" => "users#college_friends", :as => "college_friends"
and in your links
<li><%= link_to "College friends", college_friends_path(#user), :remote => true %></li>
As suggested in the comments above, you should have two partials named _college_friends.html.erb and _highschool_friends.html.erb
These will contain your HTML, which is what you're wanting to load via $('#profile-data').html()
Template Error:
You are not defining #user in college_friends and highschool_friends. So your view is requesting #user from that action, which isn't there.