Update 'like' count in Rails with Ajax - javascript

I've researched many of the other SO posts on this topic but could not seem to find one that matches my scenario.
My site has posts, which can be liked by users, incrementing the like counts. If I do this without Ajax and just use a page refresh, the counter increments properly and the button changes color, as desired. In fact, my Ajax code is changing the color of the button upon click, it's just that the like count isn't incrementing.
The server logs also seem to show that the request is being processed as JS and persisted to the database, so why is the count not being refreshed?
likes_controller.rb
def create
#post = Post.find(params[:post_id])
#like = #post.likes.create(user_id: current_user.id)
respond_to do |format|
format.html {redirect_to :back}
format.js {}
end
end
def destroy
#like = Like.find_by(post_id: params[:post_id], user_id: params[:user_id]).destroy
respond_to do |format|
format.html {redirect_to :back}
format.js {}
end
end
_like_post.html.erb
<% if current_user.already_likes?(post) %>
<%= link_to "<i class='fa fa-thumbs-up icon'></i>#{post.likes.count}".html_safe, like_path(post_id: post.id, user_id: current_user.id), method: :delete, class: 'btn btn-default stat-item active', id: "unlike-post", remote: true %>
<% else %>
<%= link_to "<i class='fa fa-thumbs-up icon'></i>#{post.likes.count}".html_safe, likes_path(post_id: post.id), method: :post, class: 'btn btn-default stat-item', id: "like-post", remote: true %>
<% end %>
create.js.erb
$("#unlike-post").show();
destroy.js.erb
$("#like-post").show();
Edit:
I was originally encountering an error with the solution because my likes routes were not nested within the posts. I changed that and also changed the subsequent like and unlike paths to reflect the nesting change (to post_likes_path and post_like_path, respectively). I also tweaked the destroy method in my likes controller to #like = Like.find(params[:id]).destroy. Finally, I passed #like as a variable in the unlike path so that that whole path reads: post_like_path(#like, post_id: post.id)

You need to rerender the partial file _like_post.html.erb when you like or unlike the post through create.js.erb and destroy.js.erb
You have $("#unlike-post").show(); but it does not update anything in the page; what .show() only does (equivalently) is change the css of the element into display: block.
Having said these, the following should make it work
_like_post.html.erb
<div id='like-post-container'>
<% if current_user.already_likes?(post) %>
<%= link_to "<i class='fa fa-thumbs-up icon'></i>#{post.likes.count}".html_safe, like_path(post_id: post.id, user_id: current_user.id), method: :delete, class: 'btn btn-default stat-item active', id: "unlike-post", remote: true %>
<% else %>
<%= link_to "<i class='fa fa-thumbs-up icon'></i>#{post.likes.count}".html_safe, likes_path(post_id: post.id), method: :post, class: 'btn btn-default stat-item', id: "like-post", remote: true %>
<% end %>
</div>
create.js.erb
$('#like-post-container').html('<%= j render partial: "likes/like_post", locals: {post: #post} %>');
destroy.js.erb
$('#like-post-container').html('<%= j render partial: "likes/like_post", locals: {post: #like.post} %>');
Note that this approach will recreate the elements. If you have JS bindings on these elements, it will be removed, and might not work properly. If you have bindings, then you'll have to update the count value directly and not like my implementation above.

Just update your js.erb fields to update the count number
create.js.erb
$("#unlike-post").html("<%= j "<i class='fa fa-thumbs-up icon'></i>#{#post.likes.count}".html_safe %>")
$("#unlike-post").show();
destroy.js.erb
$("#like-post").html("<%= j "<i class='fa fa-thumbs-up icon'></i>#{#like.post.likes.count}".html_safe %>")
$("#like-post").show();
Hope this help. :)

Related

render follow/unfollow button in rails with ajax

I have implemented follow/unfollow functionality and would like to add AJAX call to it, but I am stuck.
My partial _follow_button.html.erb for follow/unfollow which is rendered on Users->index, looks like:
<% if current_user.id != user.id %>
<% if !current_user.following?(user) %>
<%= form_for(current_user.active_relationships.build, remote: true) do |f| %>
<div><%= hidden_field_tag :followed_id, user.id %></div>
<span class="follow"><%= f.submit "Follow User", class: "btn btn-primary btn-sm" %></span>
<% end %>
<% else %>
<%= form_for(current_user.active_relationships.find_by(followed_id: user.id),
html: { method: :delete }, remote: true) do |f| %>
<span class="unfollow"><%= f.submit "Unfollow User", class: "btn btn-secondary btn-sm" %></span>
<% end %>
<% end %>
<% end %>
Then my controller for relationships looks like:
class RelationshipsController < ApplicationController
respond_to :js, :json, :html
def create
user = User.find(params[:followed_id])
#follow = current_user.follow(user)
end
def destroy
user = Relationship.find(params[:id]).followed
#unfollow = current_user.unfollow(user)
end
end
My view on user profile looks like:
<div class="col-5" style="margin-left: -5px;">
<%= render '/components/follow_button', :user => User.find_by_username(params[:id]) %>
</div>
My routes.rb have the following routes defined:
resources :users do
member do
get :following, :followers
end
end
resources :relationships, only: [:create, :destroy]
My Views folder structure has subfolders Users and Relationships. Both of them have separate controllers, and I have tried adding simple alert function 'alert("Works");' to the create.js.erb in both of those subfolders to try and match them with the controller, but none don't seem to work. This is my first Rails project, and I do not quite understand what the issue could be. Any suggestions?
Calling the partial follow/unfollow
<% if current_user.id != user.id %>
<%= render partial: 'follow_links', locals: { user: user }
<% end %>
Partial follow_links.
<% show_follow_link = current_user.following?(user) ? 'hidden' : '' %>
<% show_unfollow_link = current_user.following?(user) ? '' : 'hidden' %>
<!-- links to follow/unfollow have data-attributes that include the path to make the ajax post and the user to follow, that is used to find the link to show after the ajax call. You should use the path to the controller that will create or destroy the relationship -->
<%= link_to 'Follow', '#', { class: 'follow-user btn-success #{show_follow_link}', "data-url": follow_user_path(user.id), "data-followee": user.id } %>
<%= link_to 'Unfollow', '#', { class: 'unfollow-user btn-danger #{show_unfollow_link}', "data-url": unfollow_user_path(user.id), "data-followee": user.id } %>
Javascript for the partial. Ajax post to follow/unfollow
$('.follow-user').on("click",function() {
follow_unfollow($(this), "follow")
});
$('.unfollow-user').on("click",function() {
follow_unfollow($(this), "unfollow")
});
function follow_unfollow(target, what_to_do)
url = target.attr('data-url')
followee = target.attr('data-followee')
if (what_to_do == "follow") {
other_button = $('.unfollow-user[data-followee="'+followee+'"]')
} else {
other_button = $('.follow-user[data-followee="'+followee+'"]')
}
$.ajax( {
url: url,
type: 'post',
success: function() {
// Hide this link
target.addClass('hidden');
// Show the other link
other_button.removeClass('hidden');
},
error: function(ret) {
alert(ret.responseJSON.error);
}
});
};
Changes in your controller.
class RelationshipsController < ApplicationController
def create
user = User.find(params[:followed_id])
#follow = current_user.follow(user)
respond_to do |format|
if #follow.valid?
format.html
format.json: { render json: #follow }
return
else
format.html
format.json: { render json: { :error => 'Follow failed', :status_code :not_found } }
end
end
end
def destroy
user = Relationship.find(params[:id]).followed
#unfollow = current_user.unfollow(user)
respond_to do |format|
if #unfollow.valid?
format.html
format.json: { render json: #unfollow }
else
format.html
format.json: { render json: { :error => 'Unfollow failed', :status_code :not_found } }
end
end
end
end
An advice
An advice, also regarding your last question: I would recommend - instead of posting questions about debugging code on StackOverflow - create a good debugging environment for yourself.
Byebug or Binding pry is a good place to start, but before you can use those properly you need to understand the code you are using. I would recommend reading Working with Javascript in depth! - it really helped me getting the hang of it and understanding the dataflow of Rails and ajax.
This would, i think, break the unbreakable Stackoverflow-loop, that i myself were tied to for a long time:
loop do
puts "Try code"
sleep 1000
puts "Arrhh! an error!"
sleep 1000
puts "Posting on Stackoverflow"
sleep 1000
puts "Waiting for answer"
sleep 1000
end
I hope you figure it out!

Formatting AJAX request on index page rails

I currently have a basic to do list app that displays tasks "to do" and also tasks "completed" on the index page of the app. The model "tasks" has the attribute "completed" which I have a link to in the show page of the task:
<%= link_to "Completed", complete_task_path, method: :patch%>
The lists update on page refresh, but I'm having trouble actually making an AJAX request to update this information on the index page.
I'm unsure as to what my url should be, as I've tried to use the one provided using "rake routes" in the console.
My complete.js.erb:
$(document).ready(function() {
console.log("working");
$('.task_submit_box').click(function() {
$.ajax({url: '/tasks/:id/complete', success: function(result) {
$('.task_area').html(result);
console.log(result);
}});
});
});
My html for displaying the tasks:
<ul class="wrapper_task_list">
<% #task.each do |task| %>
<% if task.completed == false %>
<li>
<div class="task_area", id="incomplete_task_area">
<%= link_to task.title, task_path(task), class: 'wrapper_task_name'%>
<%= form_for task, remote: true do |f| %>
<%= f.check_box :completed, class: 'task_check_box' %>
<%= f.submit 'update', class: 'task_submit_box' %>
<% end %>
</div>
</li>
<% end %>
<% end %>
</ul>
And my complete method in my controller:
def complete
#task = current_user.tasks.find(params[:id])
#task.update_attribute(:completed_at, Time.now)
#task.update_attribute(:completed, true)
respond_to do |format|
format.js
format.html
end
redirect_to root_path
end
regarding the route, this is what is displayed for "rake routes" command:
complete_task PATCH /tasks/:id/complete(.:format) tasks#complete

Ajax only deletes upon refresh in Ruby app

I am trying to create a to-do list app in Rails which consists of many to-do lists and each list has as many to-do items. When I try to destroy a completed to-do item with Ajax, I have to refresh the page before it disappears. I'm new to Rails and Javascript, so any thoughts would be appreciated.
Here's my Items destroy Javascript file:
<% if #item.destroyed? %>
$('#item-' +<%= #item.id %>).hide();
<% else %>
$('#item-' +<%= #item.id %>).prepend("<div class='alert alert-danger'><%= flash[:error] %></div>");
<% end %>
The Lists#show view that calls the item partial:
%h1= #title
.row
.col-md-6
= render 'items/item', locals: {items: #items}
.row
.col-md-6
= render 'items/form', locals: {list: #list, item: #item}
= link_to "Edit", edit_list_path(#list), class: 'btn btn-success'
= link_to "Delete List", #list, method: :delete, class: 'btn btn-danger', data: { confirm: 'Are you sure you want to delete this to-do list?' }
- if #lists.nil?
= link_to "New To-do List", new_list_path, class: 'btn btn-success'
The Item partial:
- #items.each do |item|
= item.name
= link_to "", [item.list, item], method: :delete, class: 'glyphicon glyphicon-ok', remote: true
%br/
Items Controller:
class ItemsController < ApplicationController
respond_to :html, :js
def create
#list = List.find(params[:list_id])
#item = #list.items.new(item_params)
#item.user_id = current_user.id
if #item.save
redirect_to #list, notice: 'Your new To-Do Item was saved!'
else
redirect_to #list, notice: 'You forgot to enter a To-Do item. Please try again.'
end
end
def destroy
#list = current_user.lists.find(params[:list_id])
#item = #list.items.find(params[:id])
#title = #list.title
if #item.destroy
flash[:notice] = "\"#{#item.name}\" was deleted successfully."
else
flash[:error] = "There was an error deleting the list."
end
respond_with(#item) do |format|
format.html {render [#list]}
end
end
def item_params
params.require(:item).permit(:name)
end
end
Your app sends a delete request when the link created by = link_to "", [item.list, item], method: :delete, class: 'glyphicon glyphicon-ok', remote: true is clicked, but does not change the view. You need to add an event listener that catches that ajax delete request is succeeded and hides the item element in the view.
Here's a rough code to achieve that. I didn't test the code but I think you can get the idea.
item partial:
.item-container
- #items.each do |item|
.item
= item.name
= link_to "", [item.list, item], method: :delete, class: 'glyphicon glyphicon-ok', remote: true
JS:
$(document).ready(function() {
$('.item-container').on('ajax:success', function() {
$(this).closest('.item').hide();
});
};
References:
http://api.jquery.com/on/
http://tech.thereq.com/post/17243732577/rails-3-using-link-to-remote-true-with-jquery
http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to-label-Options
By the way, where do you place the piece of JavaScript code you posted? I doubt it is ever executed.
Not terribly familiar with HAML, but it looks like you are not adding an ID in your view. Your destroy.js.erb is looking for a selector like id=item-1 in order to hide it. Therefore, your item partial needs to be populated with an id in order to find it later. In ERB, I'd do:
<ul>
<% #items.each do |item| %>
<li id="item-" + item.id><%= item.name %> </li>
<% end %>
</ul>
Something like that :)

Rails is rendering partial view, when it should only be updating the partial view in an existing page

I'm trying to implement up/down-voting a post through AJAX, so that users don't have to refresh the page to see the updated score. However, when the voting links are clicked, the user is redirected to a page that only displays the partial view. The parent view is not even displayed. How can I update the partial in the index page without refreshing the entire page? I suspect that the problem lies in my 'vote' controller action. Here is my code so far:
/app/controllers/posts_controllers.rb
def vote
value = params[:type] == "up" ? 1 : -1
#post = Post.find(params[:id])
#post.add_or_update_evaluation(:votes, value, current_user)
respond_to do |format|
#I'm guessing the problem lies here.
format.html { render partial: 'score', locals: {post: #post} }
format.js
end
end
/app/views/posts/_score.html.erb
<td id="score_<%= post.id %>"><%= post.reputation_for(:votes).to_i %></td>
<% if user_signed_in? %>
<td>
<%= button_to "Upvote", vote_post_path(post, type: "up"), method: "post", class: "btn btn-success", remote: true %>
</td>
<td>
<%= button_to "Downvote", vote_post_path(post, type: "down"), method: "post", class: "btn btn-danger", remote: true %>
</td>
<% end %>
/app/views/posts/score.js.erb
$('#score_<%= post.id %>').html("<%= escape_javaScript render partial: 'score', locals: {post: #post} %>");
/app/views/posts/index.html.erb
<% #posts.each do |post| %>
<%= render partial: 'score', locals: {post: post} %>
...
<% end %>
/app/assets/javascripts/application.js
//= require jquery
//= require jquery_ujs
//= require bootstrap.min
//= require turbolinks
//= require_tree .
The js branch of your response misses the block that states which view should be rendered:
respond_to do |format|
format.html { render partial: 'score', locals: {post: #post} }
format.js { render partial: 'score', locals: {post: #post} }
end
Otherwise the rails default would be to render vote.js, since this is your controller method name. And maybe it's just a typo but your file should be named _score.js.erb (partial), just like the HTML version.
UPDATE
Having reviewed your code once more, maybe it's enough to simply rename score.js.erb to vote.js.erb, since you render the _score partial in there.
And from a design point of view, the cleanest and driest solution would be to skip the blocks for the format calls and have the files like so:
# posts_controllers.rb
def vote
value = params[:type] == "up" ? 1 : -1
#post = Post.find(params[:id])
#post.add_or_update_evaluation(:votes, value, current_user)
respond_to do |format|
format.html # this renders vote.html.erb
format.js # this renders vote.js.erb
end
end
# vote.html.erb
<%= render 'score', locals: { post: #post } %>
# vote.js.erb
$('#score_<%= #post.id %>').html("<%= escape_javaScript render partial: 'score', locals: { post: #post } %>");
# _score.html.haml
<td id="score_<%= post.id %>"><%= post.reputation_for(:votes).to_i %></td>
<% if user_signed_in? %>
<td>
<%= button_to "Upvote", vote_post_path(post, type: "up"), method: "post", class: "btn btn-success", remote: true %>
</td>
<td>
<%= button_to "Downvote", vote_post_path(post, type: "down"), method: "post", class: "btn btn-danger", remote: true %>
</td>
# index.html.erb
<%= render 'score', collection: #posts, as: :post %>

Rails update model without leaving page

What would be the best way to update a record and display a flash success notice without leaving the page?
Here is part of my code but it redirects back to root path:
View
<%= form_for #user, url: record_testimonial_path(#user) do |f| %>
<%= f.text_area :testimonial %>
<%= f.submit "Submit"%>
<% end %>
Controller
def record_testimonial
#user.update_attribute(:testimonial, params[:user][:testimonial])
flash[:success] = "Thank you!"
redirect_to root_path
end
You can redirect back:
redirect_to :back, flash: { success: t('flash.thank_you') }
Or you have to do it via remote:
<%= form_for #user, url: record_testimonial_path(#user), remote: true do |f| %>
<%= f.text_area :testimonial %>
<%= f.submit "Submit"%>
<% end %>
And in the controller:
def record_testimonial
if #user.update_attribute(:testimonial, params[:user][:testimonial])
respond_to do |format|
format.js { render 'record_testimonial', layout: false }
end
else
render nothing: true # This will silently fail. Probably not intended.
end
end
And then the record_testimonial.js.erb:
$('#some_id').html('<%= j render "some_partial", user: #user %>');
These are the most common and I would say sensible ways. If you use ajax don't forget to manually display the flash message, should be 1 line of JS.
Best way to avoid a page refresh completely would be to set the form to use :remote => true like this
<%= form_for #user, url: record_testimonial_path(#user), :remote => true do |f| %>
and then respond accordingly in the controller:
def record_testimonial
respond_to do |format|
if #user.update_attribute(:testimonial, params[:user][:testimonial])
render :success
else
render :error
end
end
end
You would need a corresponding template for success and error here, with which to handle the displaying of a success or error message.
For further info, look here.

Categories