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>
A better way...
#config/routes.rb
resources :jokes do
match :vote, via: [:post, :delete], on: :member
end
#app/controllers/jokes_controller.rb
class JokesController < ApplicationController
def vote
#joke = Joke.find(params[:id])
if request.post?
#joke.upvote_from current_user
elsif request.delete?
#joke.downvote_from current_user
end
respond_to do |format|
format.html { redirect_to :back }
format.js
end
end
end
Make a single partial view:
#app/views/jokes/_vote.html.erb
<% voted = current_user.voted_for? joke %>
<% styling = voted ? "" : "un" %>
<% arrow = voted ? "up" : "down" %>
<% method = voted ? :delete : :post %>
<%= link_to vote_joke_path(joke), method: method, remote: true, class: '#{styling}like_joke', id: joke.id do %>
<button type="button" class="btn btn-info" aria-label="Left Align">
<i class="fa fa-arrow-<%= arrow %>"></i>
<span class="badge vote_count"><%= joke.get_upvotes.size %></span>
</button>
<% end %>
#app/views/jokes/show.html.erb
<%= render partial: "vote", locals: { joke: #joke } %>
This way, when you invoke vote.js.erb from your JokesController, you'll be able to use the following:
#app/views/jokes/vote.js.erb
$("<%=j #joke.id %>").html("<%=j render partial: "jokes/vote", locals: { joke: #joke } %>");
Related
I am working on a simple website which has posts in forms of songs, now I have implemented like and dislike feature, but when I click on like/dislike it renders the numbers of likes/dislikes on all posts. And when I reload the page it returns them to normal. Now I would like it to change only the numbers for that particular post, without affecting the numbers on other posts?
My view for posts:
<div class="col-6">
<% for #s in #songs %>
<div class="card border-secondary mb-1">
<div class="card-header">
<div class="col-1">
<%= image_tag User.find(#s.user_id).user_image.url, :size=>"50x50" %>
</div>
<div class="col-11 text-muted">
<a href="/user/<%= User.find(#s.user_id).username %>" class="info-col">
<%= User.find(#s.user_id).username %>
</a>
- <%= #s.created_at.to_formatted_s(:short) %>
</div>
</div>
<div class="card-body">
<h4 class="card-title">
<%= #s.title %>
</h4>
<p class="card-text"><%= simple_format(#s.content) %></p>
</div>
<div class="card-footer text-muted">
<div class="col-12">
<div class="row">
<div class="col-3" id="song_like">
<%= render '/components/song_like' %>
</div>
<div class="col-3" id="song_dislike">
<%= render '/components/song_dislike' %>
</div>
<div class="col-4" id="song_comment">
<%= render '/components/song_comment' %>
</div>
</div>
</div>
</div>
</div>
<% end %>
</div>
The song_like partial has the following code:
<%= link_to like_song_path(#s.id, like: true), remote: true, method: :post do %>
<i class="fa fa-thumbs-o-up fa-lg" aria-hidden="true"></i>   <span class="songlikes">
<%= #s.thumbs_up_total %>
</span>
<% end %>
The song_dislike partial has the following code:
<%= link_to like_song_path(#s.id, like: false), method: :post, remote: true do %>
<i class="fa fa-thumbs-o-down fa-lg" aria-hidden="true"></i>   <span class="songdislikes">
<%= #s.thumbs_down_total %>
</span>
<% end %>
And my 'like' controller is like:
def like
#s = Song.find(params[:id])
#like = Like.create(like: params[:like], user: current_user, song: #s)
respond_to do |format|
if #like.valid?
format.html
format.js
else
format.html
format.js
end
end
end
This is how like.js.erb looks like:
$('.songlikes').html("<%= #s.thumbs_up_total %>");
$('.songdislikes').html("<%= #s.thumbs_down_total %>");
Here is the part of routes.rb file:
resources :songs do
member do
post 'like'
end
end
I am assuming there is some issue on rendering or jquery, but can't figure it out. Do You have any instructions?
EDIT: You should remove the redirect_to in your f.html?
This reloads the page - and the js will therefore not work.
An Ajax call is per definition not reloading the page.
If you remove those lines and try the following:
There's no new data in your jQuery.
You need a global attribute, so the view can find the data from your controller.
For example:
#like = ...
Then in your view you can add a local to be rendered:
$("#song_like").load("<%=j render partial: '/components/song_like', locals: {like: #like} %>");
But, you need to change your partial to be a partial. And change the variable to like so it will be rendered in the right way.
Hope it helps!
UPDATE
If you want to hit a specific song you can render it with each and then assign an id to your post's class
For example:
<% #posts.each do |post| %>
<div class="post-content">
... other stuff ...
<div class="likes like-post-<%= post.id %>">
.. number of likes
</div>
<%= link_to "Like", link_route_path(post) %>
</div>
<% end %>
# controller
# So you can use the id in your js view
#id = params[:id]
# view.js
$(".like-post-" + "<%= #id %>").html("<%= #s.thumbs_up_total %>");
Hope the idea helps.
change rendering like this:
<div class="col-3" id="song_like">
<%= render partial: '/components/song_like', locals: {s: #s, like: true} %>
</div>
then in partial:
<%= link_to like_song_path(s.id, like: like), method: :post, remote: true do %>
<i class="fa fa-thumbs-o-up fa-lg" aria-hidden="true"></i>   <%= s.thumbs_up_total %>
<% end %>
and in controller of like_song
should define
#s= Song.find(params[:id])
like = Like.create(like: params[:like], user: current_user, song: #song)
#like = !like.like # opposite to your current status
rest of the things good and then in like.js.erb
$('#songs_like').html("<%= escape_javascript(render partial: '/components/song_like', locals: { s: #s, like: #like } ) %>")
My application contains a very simple login and two separate AJAX calls. I have a loading spinner that is hidden automatically whenever a page is ready it is contained in index.js
$(function() {
$("loading").hide();
$("#scan_button").on("ajax:success", function(e, data, status, xhr) {
$("#standard_results").hide();
$("#results").show();
$("#results").html(data);
});
$(document).ajaxStop(function() {
$("#loading").hide();
});
$(document).ajaxStart(function() {
$('#loading').show();
});
});
When a user logs in the following happens
class SessionController < ApplicationController
def create
user = User.find_by(username: params[:session][:username])
if(user.password == params[:session][:password])
log_in user
redirect_to root_path
end
end
def destroy
logout
redirect_to root_path
end
end
The route path takes me back to 'home#index' for which the index.js file exists. When the user logs in the redirect happens and the loading spinner is hidden and all JavaScript executes as expected.
When the user hits log out however the redirect occurs, but the loading spinner is shown and no JavaScript ever gets executed. Here is my index.html.erb file that contains the JavaScript.
<% content_for :title, "Network Monitor" %>
<div class="container-fluid">
<nav class="navbar narvbar-dark bg-inverse">
<span class="navbar-brand">Network Monitor</span>
<%= link_to "Scan", scan_path, remote: true, class: "btn btn-secondary", id: "scan_button" %>
<% if logged_in? %>
<span class="pull-xs-right"><%= link_to "Logout", logout_path, class: "btn btn-secondary" %></span>
<% else %>
<%= form_for(:session, url: login_path, :html => {:class => 'form-inline pull-xs-right'}) do |f| %>
<%= f.text_field :username, class: 'form-control', placeholder: "Username" %>
<%= f.password_field :password, class: 'form-control', placeholder: "Password" %>
<%= f.submit "Log in", class: 'btn btn-primary', id: 'login_button' %>
<% end %>
<% end %>
</nav>
<div id="loading">
<%= image_tag "loading.gif", class: "loading_gif" %>
</div>
<div class="row">
<div class="col-md-12">
<div id="scan_bar">
<h3>Welcome to your network monitor!</h3> <br />
<p>To get started hit the scan button in the top right corner, this will detect the computers that are alive on your network and show them below.</p>
</div>
</div>
</div>
<div class="row">
<div id="results">
</div>
<div id="standard_results">
</div>
Solved. It was as simple as using
$(document).on('turbolinks:load', function() {});
Instead of what I had posted, this seems to be the way that Rails 5 and turbolinks want you to use, and it worked.
According to the
turbolinks document, I use the event page:change to solve the problem:
$(document).on("page:change", function(){...});
It works for me.
In case others run into this. If you are using an href or link_to try doing something like this:
<li><%= link_to "Home", jobs_path, method: :get %></li>
...where you specify the get method in the request.
I have a directory app that has comments. I add and delete comments with remote: true and I want to fade them out in my view with Jquery.
How do I go about getting a reference to the comment I want to fade out in the view? I can delete the comment just fine, but I don't know how to fade the comment out in my view
Here is my controller:
class CommentsController < ApplicationController
def create
business = Business.find(params[:business_id])
new_comment = business.comments.create
new_comment.comment = params[:comment_text]
new_comment.save
respond_to do |format|
format.html {redirect_to business}
format.js
end
end
def destroy
business = Business.find(params[:business])
comment = Comment.find(params[:id])
comment.destroy!
params[:id] = business.id
respond_to do |format|
format.html {redirect_to business_url(id: params[:id])}
format.js
end
end
end
Comments are rendered in my business view like this:
<aside class="comment-section">
<h4 class="thin-grey-underline"><span class="glyphicon glyphicon-comment glyphicon-left-space"></span> Reviews </h4>
<% if !#business.comments.empty? %>
<% #business.comments.each do |comment| %>
<div id="comments">
<%= render partial: 'comment', locals: {comment: comment, business: #business} %>
</div>
<% end %>
<% else %>
<p> No reviews yet... </p>
<% end %>
<%= render partial: 'comments_form' %>
</aside>
my single comment partial:
<div class="row single-comment vertical-align">
<div class="col-sm-12 col-md-3">
<div class="profile-image" style="background: url('<%= business.profile_image_url(:thumb).to_s %>') no-repeat; background-position:center"></div>
</div>
<div class="col-sm-12 col-md-9">
<h5><%= comment.title %></h5>
<p><%= comment.comment %></p>
<%= button_to(business_comment_path(#business.id,comment.id), {method: :delete, remote: true, class:"btn btn-danger delete-comment-button pull-right", params:{id: comment.id, business: business.id}} ) do%>
<i class="glyphicon glyphicon-trash"></i>
<% end %>
</div>
</div>
the form partial to create a new comment:
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="comments-form">
<%= form_tag(business_comments_path(#business), html: { id: "comment-form" }, method: 'post', remote: true) do %>
<h5 class="thin-grey-underline"><span class="glyphicon glyphicon-pencil glyphicon-left-space"></span> Write a Review </h5>
<%= text_area_tag(:comment_text,nil, class: "comment-form-input")%>
<%= hidden_field_tag 'business_id', params[:id] %>
<%= submit_tag("Comment", class:"btn btn-primary") %>
<% end %>
</div>
</div>
</div>
If I have a list of comments, how do I delete a specific comment by clicking on the delete link and also have it removed from my view in a fadeout manner?
I have a destroy.js.erb file that gets called, but I don't know how to get a reference to fade out the specific comment.. not sure if I am explaining myself clearly here
You could try something like this:
In your comment partial:
<div id="comment_<%= comment.id %>" class="row single-comment vertical-align">
destroy.js.erb:
$('#comment_<%= #comment.id %>').remove();
Knockoutjs can do that The "if" binding
First step is to make sure that the thing you want to delete has a unique id, and that this id is easy to generate. I patched AR::Base with an instance method called dom_id for this, which is like so:
def dom_id
"#{self.class.name.underscore}-#{self.id}"
end
which means if i'm rendering out a list of Foo objects i can do something like
<%= render :partial => "foos/foo", :collection => #foos %>
and in the partial do this
<li id="<%= foo.dom_id %>">
....
</li>
and the id will be something like "foo-123".
Now, in my destroy.js.erb action, after deleting a foo object, i know that it's dom id on the page can be got with #foo.dom_id. To fade it out and delete it i would then do
page << "$('##{#foo.dom_id}').fadeOut('slow', function(){$(this).remove();});"
You can use helper dom_id method (docs):
Change <div id="comments"> to <div id="<%= dom_id comment %>"> or add id attribute to div.single-comment
In js response do $('#<%= dom_id #comment %>').fadeOut();
Another option is to bind to jQuery event like this:
$('body').on('ajax:success', '.delete-comment-button', function(event) {
$(event.original_target).closest('.single-comment').fadeOut();
})
Then you can just return status 2XX for the delete request
In my Rails 4 app, I have a Post model with a custom approval attribute.
I am trying to update this custom approval attribute from the Posts#Show page when the user clicks on one of three particular links set with remote: true.
The goal is to reload only the partial displaying the content of this attribute, and not the entire page.
Here is my Post show.html.erb view:
<div id="post_show_approval">
<%= render 'approval' %>
</div>
And here is my _approval.html.erb partial, located in the app/views/posts folder:
<ul>
<li>
<% if #post.approval == "ok" %>
<span class="ok_green">
<span class="glyphicon glyphicon-ok"></span>
Post Approved
</span>
<% else %>
<span class="approval_blue" %>
<%= link_to post_path(:id => #post.id, "post[approval]" => "ok"), :class => "post_appoval_link", remote: true, :method => :patch do %>
<span class="glyphicon glyphicon-ok"></span>
Approve this post
</span>
<% end %>
<% end %>
</li>
<li id="post_show_require_edits">
<% if #post.approval == "edit" %>
<span class="edit_yellow">
<span class="glyphicon glyphicon-repeat"></span>
This post requires edits
</span>
<% else %>
<span class="approval_blue" %>
<%= link_to post_path(:id => #post.id, "post[approval]" => "edit"), :class => "post_appoval_link", remote: true, :method => :patch do %>
<span class="glyphicon glyphicon-repeat"></span>
Require edits for this post
</span>
<% end %>
<% end %>
</li>
<li id="post_show_to_be_deleted">
<% if #post.approval == "remove" %>
<span class="remove_red">
<span class="glyphicon glyphicon-remove"></span>
This post needs to be deleted
</span>
<% else %>
<span class="approval_blue" %>
<%= link_to post_path(:id => #post.id, "post[approval]" => "remove"), :class => "post_appoval_link", remote: true, :method => :patch do %>
<span class="glyphicon glyphicon-remove"></span>
Mark this post as to be deleted
<% end %>
</span>
<% end %>
</li>
</ul>
As you can see, all the links with the post_approval_link class are set with remote: true.
Then, I have updated my PostsController as follows:
def update
respond_to do |format|
if #post.update(post_params)
format.html { redirect_to post_path(#post) }
format.json { render :show, status: :ok, location: #post }
format.js
else
format.html { render :edit }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
And I have created an update.js.erb file in the app/views/posts folder:
$('#post_show_approval').reload(true);
—————
UPDATE: as alternatives to the above line, I also tried
$('#post_show_approval').hide().show();
and
$('#post_show_approval').toggle().toggle();
But none of these seem to work.
—————
UPDATE 2: here is another thing I tried:
$('#post_show_approval').load('<%= j render "approval" %>');
But it is not working either.
—————
UPDATE 3: when I try only
$('#post_show_approval').hide();
or
$('#post_show_approval').toggle();
The div does disappear.
But I still did not find a way to make it reappear with its content updated.
—————
UPDATE 4: also, when I use:
$('#post_show_approval').append('<%= j render(partial: "approval") %>');
The approval partial does load with its updated content... the initial content does not disappear, so every time we click on one of the links, a new line of content stacks up.
—————
However, when I click on one of the three links with the post_approval_link class, the appoval attribute of the post is actually updated to the correct value, but the #post_show_approval div is not reloaded and I need to refresh the page to see the actual changes.
What am I missing in the implementation of this AJAX feature?
The problem was that I was not using the right JavaScript method.
With:
$('#post_show_approval').html('<%= j render(partial: "approval") %>');
It is now working like a charm.
Hi guys I really need help here.
I'm making a todo app and displaying each task through an iteration. Next to each task I have a pencil glyphicon.
When I click on it, I want there to be a popover containing an edit form and edit the task on the spot. I'm having a ton of troubling rendering that form.
***** UPDATE *****
Clark helped me get the popover to work. But The form inside the popover is acting like a link instead of a form so I can't type in the text area.
This is my index.html.erb
<div id='user_tasks'>
<% #tasks.each do |task| %>
<ul>
<li>
<%= task.description %>
<%= link_to edit_task_path(task), :rel => 'popover', method: 'get', remote: 'true' do %>
<span class = 'edit_task_<%=task.id%> glyphicon glyphicon-pencil'> </span>
<% end %>
</li>
</ul>
<% end %>
</div>
This is the edit.js.erb
$(function(){
$('.edit_task_<%=#task.id%>').popover({
html: true,
title: 'edit',
content: <%= escape_javascript render 'edit_form', task:#task %>
}).popover('show'); });
and my controllers look like this:
def index
#new_task= Task.new
#tasks= Task.all
end
def create
#new_task= Task.new(task_params)
#task.save
redirect_to :back
end
def edit
#task= Task.find(params[:id])
end
_edit_form.html.erb looks like this: This isn't the edit form that I plan to use. Its just a copy of the create task form. I plan to fix it once I get the popover working.
<%= form_for task, :html=>{:class=> 'form-inline'} do |f| %>
<div class='form-group'>
<%= f.text_field :description, placeholder: 'create a task' %>
<%= button_tag(type:'submit', class:"btn btn-default btn-xs") do %>
<span class='glyphicon glyphicon-plus'></span>
<% end %>
</div>
<% end %>