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 :)
Related
I have a projects/show.html.erb page. A project has_many project_messages and from the projects/show.html.erb page, a user can create a new project_message successfully, however, when the new project_message is created through a _formpartial, the page refreshes.
I want to use :remote => true to add project_messages to the project without having to refresh the page.
Please see the code I used below. This does not work and the page still refreshes. I am new to rails so any help would be greatly appreciated.
Please see the code for each file below
In projects/show.html.erbto display the project_message and create a new project_message, I have the following code which is successful:
<div class="au-chat au-chat--border">
<div class="au-message-list">
<% #project.project_messages.each do |project_message| %>
<%= render partial: 'project_messages/project_message', locals: { project_message: project_message } %>
<% end %><br>
</div>
<div class="au-chat-textfield">,
<%= render partial: 'project_messages/form', locals: { project_message: #project_message } %>
</div>
</div>
In the project_messages_controller.rb file the create method is as follows, I have added format.js { }
def create
#project_message = ProjectMessage.new(project_message_params)
#project_message.user_id = current_user.id if current_user
#project_message.project_id = current_user.team.project_id if current_user
respond_to do |format|
if #project_message.save
format.html { redirect_to #project_message.project }
format.json { render :show, status: :created, location: #project_message }
format.js { }
else
format.html { render :new }
format.json { render json: #project_message.errors, status: :unprocessable_entity }
end
end
end
The project_message _form partial then includes the :remote => true
<%= form_with(model: project_message, local: true, :remote => true) do |form| %>
<% if project_message.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(project_message.errors.count, "error") %> prohibited this project_message from being saved:</h2>
<ul>
<% project_message.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<%= form.text_field :pMessage, id: :project_message_pMessage, :class => 'au-input au-input--full au-input--h65', placeholder: 'Type a message' %>
<div class="actions" >
<%= form.submit "Send" %>
</div>
<% end %>
I then created a new create.js.erb file in views/project_messages/create.js.erb and added the following code:
# confirm file called
console.log('create.js.erb file');
# add new comment to end of comments section
$("#project_message").append("<%= j render(#project_message) %>");
With the code above, the page still refreshes.
When I remove local: true from the project_message _form partial, it does not refresh and creates the model but the projects/show.html.erb view is not updated!
I used the following link here and also other posts on stackoverflow
respond_to do |format|
format.js { render 'project_messages/create.js'}
end
it seems you are using Rails 5
remove remote: true and local: true from your code
read about this in here
and make sure that you a html element with id = project_message
your code is $("#project_message").append("<%= j render(#project_message) %>");
so check your HTML code , is there any element with ID like that
you can use traditional form_for http://guides.rubyonrails.org/working_with_javascript_in_rails.html#remote-elements
update below code in your views
<%= form_for project_message, :remote => true do |form| %>
and make sure that you a html element with id like <div class="au-message-list" id="project_message">
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!
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
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. :)
So I'm using the acts_as_commentable_with_threading for a comment system similar to Reddit.
So on the show page of an item, I have a form_for that has (items/show.html.haml):
- if user_signed_in?
%h5 Have something to say?
= form_for([#item, #new_comment], remote: true) do |f|
.form-group
= f.text_area :body, class: "form-control", rows: "3"
= f.hidden_field :user_id, value: current_user.id
.form-group
= f.submit "Submit", class: "btn btn-sm"
With a controller that does (items_controller.rb):
def show
#item = Item.find(params[:id])
#comments = #item.comment_threads
if user_signed_in?
#new_comment = Comment.build_from(#item, current_user.id, "")
end
end
This will then have a create.js.erb that will append the newly made comment onto the page
$('#comments').append("<%= escape_javascript(render partial: 'comment', locals: { comment: #comment } ) %>");
if ("<%= #comment.body %>") {
$("#comment_body").val('')
}
This by itself, works. I render each comment, and then inside the partial that each comment is rendered from, if they have any children, I render the child comments too.
Such as...:
- if !#comments.empty?
= render partial: 'comments/comment', collection: #item.root_comments, as: :comment
However each comment is able to have a reply, and those replies can have replies of their own (again, like Reddit). So when I attempt to do the same thing with the child replies, it gives me a 500 error.
= form_for([#item, #new_comment], remote: true, html: { class: "comment-reply", id: "replyto_#{comment.id}" }) do |f|
.col-md-5
.form-group
= f.text_area :body, class: "form-control", rows: "3"
= f.hidden_field :user_id, value: current_user.id
= f.hidden_field :parent_id, value: comment.id
.form-group
= f.submit "Submit", class: "btn btn-sm"
%div{style: "clear:both;"}
So my question is, how do I make a form_for for the child comments, and then (probably) create a new js.erb so it won't "append", but rather, rerender the parent comment (so that would in turn render the newly made child comment).
I think I may need to make a new create, so like a create_child, but then what does the form_for turn into?
Figured it out myself.
Basically inside the comments_controller.rb I had to find out if the new reply had a parent_id.
def create
#item = Item.find(params[:item_id])
#all_comments = #item.comment_threads
if (params[:comment].has_key?(:parent_id))
#parent = Comment.find(params[:comment][:parent_id])
end
#comment = Comment.build_from(#item, current_user.id, params[:comment][:body])
if #comment.save
if #parent
#comment.move_to_child_of(#parent)
end
respond_to do |format|
format.js
end
else
flash.now[:error] = "Comment was not submitted."
redirect_to root_path
end
end
And then inside my create.js.erb I needed to find out if it also had a parent_id:
if ("<%= #comment.parent_id %>") {
$('.comment_<%= #comment.parent_id %>').append("<%= escape_javascript(render partial: 'comment', locals: { comment: #comment } ) %>");
$("#replyto_<%= #comment.parent_id %>").val('');
$("#replyto_<%= #comment.parent_id %>").toggle();
}
else {
$('#comments').append("<%= escape_javascript(render partial: 'comment', locals: { comment: #comment } ) %>");
if ("<%= #comment.body %>") {
$("#comment_body").val('');
}
}
This allows the child comments to be appended (via JavaScript) and for them to be put under the correct parent.