I have a view of listings and each listing has a 'favorite' button associated with it. This button submits an AJAX form, either liking or unliking the listing if it has already been liked. Submitting the form calls a like.js.erb method or destroy.js.erb which changes the state of the button.
However, since the js.erb files target the #id of the element I am changing, this only works for the first element. I also can't use a class because that changes all elements.
What's the best way to go about allowing users to like and unlike listings, and changing the view without reloading the page?
I tried giving each of the IDs a unique number based on the listing ID, but can't pass the listing ID to the js.erb files so that doesn't work.
Here is some relevant code:
/listings/_like.html.erb
<%= link_to like_listing_path(#listing), method: :put, :remote => true, :class => "like-btn" do %>
<i class="fa fa-heart-o"></i>
<% end %>
/listings/like.js.erb
$("#like-form").html("<%= escape_javascript(render('listings/unlike')) %>")
$("#like-size").html('<%= #listing.get_likes.size %>')
/listings/unlike.html.erb
<%= link_to unlike_listing_path(#listing), method: :put, :remote => true, :class => "unlike-btn" do %>
<i class="fa fa-heart liked"></i>
<% end %>
/listings/unlike.js.erb
$("#like-form").html("<%= escape_javascript(render('listings/like')) %>")
$("#like-size").html('<%= #listing.get_likes.size %>')
Thank you!
The Rails Way
Just append the ID of the listing to their ID in your erb (since your form is not there I am assuming some structure as the following):
<div id="like-form-<%= #listing.id %>">
<!-- your content -->
</div>
and do the same in your JS file:
$("#like-form-<%= #listing.id %>").html("<%= escape_javascript(render('listings/unlike')) %>")
$("#like-size-<%= #listing.id %>").html('<%= #listing.get_likes.size %>')
This way you will always target the correct element.
JS way
Let's say that you have a list of listings generated through a loop and each item has the following markup:
<div class="listing">
<!-- your content -->
<div class="like-form">
<%= link_to like_listing_path(#listing), class: "like-btn" do %>
<i class="fa fa-heart-o"></i>
<% end %>
<%= link_to unlike_listing_path(#listing), class: "unlike-btn" do %>
<i class="fa fa-heart liked"></i>
<% end %>
</div>
</div>
Now you could do something write some JS like this:
$(".like-btn, .unlike-btn").click(function(e) {
e.preventDefault();
var el = $(this);
$.ajax({
url: el.attr("href"),
method: post
}).done(function(data) {
el.parent().html(data);
});
});
Same thing applies for the number of likes.
A word on REST
I have notices that you have two different paths to like and unlike so I am assuming that you have added those paths manually to your routes.rb. If you really want to leverage the power of REST you really only need a single path to your listing like and then use POST to create a like and DELETE to remove a like (i.e. unlike).
How about using data attributes?:
<%= link_to like_listing_path(#listing), method: :put, :remote => true, :class => "like-btn", :"data-id" => user.id do %>
<i class="fa fa-heart-o"></i>
<% end %>
Then on your like action
def like
#listing = Listing.find params[:"data-id"]
#listing.like!
end
Related
I have a form which I use to create a list. I want to give the user the ability to create new forms for new items to be saved for future list making. I want to have a "+" button to add create a new form via JavaScript. It was working great on my test user, but things were getting cluttered so I made a new user to start fresh. When I tried to load my new list page I get his with this:
undefined method `id' for nil:NilClass
Specifically, it's pointing to this bit in my JavaScript:
$(this).parents(':eq(1)').append("<%= escape_javascript render partial: 'shared/user_item_form', locals: {f: #f, category_id: #category.id} %>");
I need to pass these variables to any new form that's going to be rendered, but these variables rely on data that is passed from the server or database or something. It seems like it's trying to run my JavaScript first, despite me declaring $(document).ready.
Here is the form in it's entirety.
Line 26 is where this problem starts, where I declared some class variables in order for them to reach my javascript down below.
Line 43 is where I render the first partial, the same one that will be duplicated when the user presses "+". When the user presses that button I want the same exact form to pop up underneath it.
https://pastebin.com/ieLYfYm4
I'm a real noob when it comes to combining JS and Rails, and I've been struggling a lot with this part of my project. I don't really understand what gets loaded first or where my JS is supposed to go. What I have hacked together seems to work fine though until now. Not sure why it was even working at any point if this now a problem. Maybe my browser stored something in the cache that was preventing this error from being raised?
Code if you can't look at the pastbin
<div class="new-list">
<h2>New List</h2>
<%= form_for #list, url: list_path, html: {class: "form-signin"} do |f| %>
<%= f.text_field :name, placeholder: "Name your list (optional)" %>
<!-- Category -->
<div class="categories">
<% current_user.categories.each do |category| %>
<h3 class="category">
<%= category.name %>
</h3>
<!-- Items -->
<div class="dropdown-button" data-toggle="collapse" data-target="#<%= category.name %>">
<span class="glyphicon glyphicon-chevron-down"></span>
</div>
<!-- Dropdown div -->
<div id="<%= category.name %>" class="collapse">
<div class="table-responsive">
<!-- User Item table -->
<table class="items">
<tr>
<th>Add</th>
<th>Name</th>
<th>Price</th>
<th>Qnty</th>
</tr>
<% category.user_items.each do |item| %>
<%= f.fields_for l = #list.list_items.build, index: l.id do |list_item| %>
<% #category = category %>
<% #f = f %>
<tr>
<td><%= list_item.check_box :user_item_id, { checked: false }, item.id, nil %></td>
<td><%= item.name %></td>
<td><%= item.price %></td>
<td><%= list_item.number_field :quantity,
min: 1,
class: "num"%></td>
</tr>
<% end %>
<% end %>
</table>
</div>
<h3>Create a new item here:</h3>
<%= render partial: 'shared/user_item_form', locals: {f: f, category_id: category.id} %>
<h4><span class="glyphicon glyphicon-plus" id="add-item"></span>Add More</h4>
</div>
<% end %> <!-- current_user.categories -->
<br>
<%= f.submit "Submit" %>
</div>
<% end %>
</div>
<!-- dropdown button script -->
<script>
$(document).ready(function () {
$('.data-toggle').collapse();
addNewItemForm();
});
function addNewItemForm(){
$('#add-item').click(function(){
$(this).parents(':eq(1)').append("<%= escape_javascript render partial: 'shared/user_item_form', locals: {f: #f, category_id: #category.id} %>");
});
}
</script>
The problem is that the javascript function addNewItemForm() at the bottom of your script is evaluated not when you invoke it, but when the page is fully rendered at first, and at that moment and at that place, when Rails tries to evaluate the following string:<%= escape_javascript render partial: 'shared/user_item_form', locals: {f: #f, category_id: #category.id} %> the #category variable is out of scope and undefined.
Actually, you need to make another server request on click of a button with corresponding params and let the server to do the work: render new partial and invoke javascript to alter HTML.
You can do that easily with the following example:
<%= link_to 'Add New Item', [:new, category, :items], remote: true %>
("remote: true" will make an ajax request).
You will get category in URL params in your controller and can instantiate it again:
#category = Category.find(params[:category_id])
and then respond with the following code (new.js.erb):
<%= j render 'shared/user_item_form', category_id: #category.id %>
(which is simply a shorthand of your line)
Here is the reference to documentation with similar example: http://guides.rubyonrails.org/working_with_javascript_in_rails.html#a-simple-example
In my Rails 4, I have the following models:
class Calendar < ActiveRecord::Base
has_many :administrations
has_many :users, through: :administrations
has_many :posts
has_many :comments, through: :posts
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
class Post < ActiveRecord::Base
belongs_to :calendar
has_many :comments
end
The post objects are displayed the Calendars#Show view of the calendar they belong to.
The post model has an :approval custom attribute.
I want to update this custom attribute from the Calendars#Show view thanks to a set of three Ajax links and have implemented this feature as follows:
<%= link_to post_path(:id => post.id, "post[approval]" => "ok"), :remote => true, :method => :patch do %>
<span class="glyphicon glyphicon-ok"></span>
<% end %><br/>
<%= link_to post_path(:id => post.id, "post[approval]" => "edit"), :remote => true, :method => :patch do %>
<span class="glyphicon glyphicon-repeat"></span>
<% end %><br/>
<%= link_to post_path(:id => post.id, "post[approval]" => "remove"), :remote => true, :method => :patch do %>
<span class="glyphicon glyphicon-remove"></span>
<% end %>
This is currently working pretty fine, as I can see with the Rails console that the :approval attribute is updated when I click one of the links above.
Now, at the same time as I update the :approval attribute, I would like to update the style of the links.
For instance, I would like the link that is clicked on to become green / yellow / red (respectively for the first, second and third link) and the other to become grey.
I understand I will have to define some CSS classes for each style, such as:
.ok-green {
color: green;
}
.edit-yellow {
color: yellow;
}
.remove-red {
color: red;
}
.link-grey {
color: grey;
}
What I don't understand is how I am going to update these classes in my view when a user clicks one of the links above.
—————
UPDATE: Perhaps I could use Coffeescript, as recommended here, to update the class of the link. Where should the Coffeescript code go, though?
—————
UPDATE 2: With a simple if else statement in the view, I tried the following:
<% if post.approval == "ok" %>
<span class="ok_green">
<% else %>
<span class="approval_grey" %>
<% end %>
<%= link_to post_path(:id => post.id, "post[approval]" => "ok"), :remote => true, :method => :patch do %>
<span class="glyphicon glyphicon-ok"></span>
<% end %>
</span><br/>
<% if post.approval == "edit" %>
<span class="edit_yellow">
<% else %>
<span class="approval_grey" %>
<% end %>
<%= link_to post_path(:id => post.id, "post[approval]" => "edit"), :remote => true, :method => :patch do %>
<span class="glyphicon glyphicon-repeat"></span>
<% end %>
</span><br/>
<% if post.approval == "remove" %>
<span class="remove_red">
<% else %>
<span class="approval_grey" %>
<% end %>
<%= link_to post_path(:id => post.id, "post[approval]" => "remove"), :remote => true, :method => :patch do %>
<span class="glyphicon glyphicon-remove"></span>
<% end %>
</span>
This does change the color of my links... but only after page refresh, not in "real-time" when we click on the links.
Any idea how to implement a similar idea but in "real-time" with Ajax?
—————
Any idea how to achieve that?
There are a lot of possible paths. I'll give you a simplified one.
First, since you're going to be updating pieces of your template, you'll want to extract them into partials
views/calendars/show.html.erb
<% #calendar.posts each do |post| %>
<% render partial: 'post_partial', object: post %>
<% end %>
You can read more about the render partial syntax here.
Next, I'm going to wrap your partial inside a div, whose class is the approval and whose id uses a Rails-provided helper called dom_id, which will create something like id="post_123".
views/calendars/_post_partial.html.erb
<div class="post-#{ post.approval }" id="#{ dom_id(post) }>
<!-- all of your links to post_path -->
</div>
The classes of post-ok, etc. will allow you to write some css like
.post-ok .glyphicon-ok {
color: green;
}
.post-ok .glyphicon-edit {
color: grey;
}
That way, when your approval status changes, CSS handles changing colors, and your HTML can update with any future statuses you come up with.
Now, to respond to your remote: true links, you're going to need to respond with a .js.erb template to swap out the old post_partial and swap in the new one.
Note that your link is specifically pointing to update, which is why that's my view name. You might want to create a specific route (maybe call it update_status), since you could possibly be updating posts in another form already.
views/posts/update.js.erb
$('#<%= dom_id(#post) %>').replaceWith("<%= j render partial 'calendars/post_partial', object: #post %>");
That JavaScript is specifically using jQuery (which should be in your Gemfile by default) to do the following:
Grab an item on the page based on its ID, in this case, the ID of the updated Post...
...replace the entire DOM element that got grabbed...
...with the partial called 'post_partial', but formatted to be JavaScript safe
Let me know how that goes. DOM-replacement with JS templates can be a rabbit hole, and at some point you're going to need to determine the cost-benefit of when and where to AJAX. But, this is how you'd do it.
Using Rails 4 and Ruby 1.9.3. Styling is handled by an internally developed gem based on bootstrap 3.
I've been looking around for answers to this and have found a number of different examples that show how to do this with a basic list in a view. The railscast is one such example I've looked at.
http://railscasts.com/episodes/147-sortable-lists
However, I am trying to achieve this in a rails nested form using a partial but having no success. Sadly I am new to ruby, rails and have no prior knowledge of javascript so this is all a step learning curve.
My service model relates to a places model through service places. The service_places.position field holds the order of the stops.
My nested form (_form.html.erb) for services is shown below:
<!-- Adds the Service_Places (stops) associations via partial (sort applied in model) -->
<div>
<div class="links" id="sortable">
<%= link_to_add_association 'Add Stop', f, :service_places, :class => "btn btn-default", :data => {"association-insertion-method" => "after" } %>
</div>
<%= f.fields_for :service_places do |service_places| %>
<%= render 'service_place_fields', :f => service_places %>
<% end %>
</div>
My service_place partial is shown below:
<!-- This will hold the partial form for service places -->
<div class="nested-fields">
<%= f.label :service_place, "Stops", :class=>"col-sm-2 control-label" %>
<div class="col-sm-1">
<%= f.text_field :position, :class=> "form-control", :placeholder => "Position" %>
</div>
<div class="col-sm-3">
<%= f.collection_select :place_id, Place.where('active = true').order(:place_name), :id, :place_name, :prompt => "Select Place" %>
</div>
<div>
<%= link_to_remove_association "Remove Stop", f, :class => "btn btn-default" %>
</div>
</div>
I started looking at trying to assign an ID to each of the service_place partials DIV tags but couldn't get it to work.
The questions I would like to know are:
1) Is it possible to allow user to reorder items within forms? and save the new order in the server?
2) If it is possible could someone give me a hint on how to go about doing this.
Thanks in advance for taking time to look at this post.
My colleague showed me a way to achieve this using the "acts as list" gem.
His blog can be found here:
http://nicholshayes.co.uk/blog/?p=344
To get this working I made the amendments as shown below.
1) In my service model I added these three methods:
# Used in the positioning of service places using the acts_as_list gem
def method_missing(symbol, *args, &block)
if acts_as_list_method?(symbol)
pass_method_to_service_service_place(symbol, args.first)
else
super
end
end
# Used in the positioning of service places using the acts_as_list gem
def pass_method_to_service_service_place(symbol, service_place)
raise "A Service_Place is needed for re-ordering places" unless service_place.kind_of? ServicePlace
service_place.send(symbol) if service_place
end
# Used in the positioning of service places using the acts_as_list gem
def acts_as_list_method?(symbol)
ActiveRecord::Acts::List::InstanceMethods.instance_methods.include?(symbol.to_sym)
end
2) Added route's
resources :services, :concerns => :paginatable, only: [:create, :destroy, :edit, :index, :new, :show, :update] do
# Required for the re-ordering of the routes
resources :service_places do
member do
get :move_up
get :move_down
end
end
end
3) Amend the SERVICE show.html
<% #service.service_places.reorder("service_places.position asc").each do |serviceplace| %>
<dd>
<% if user_signed_in? %>
<% if #service.first?(serviceplace) %>
<%= link_to('', move_up_service_service_place_path(serviceplace, service_id: #service), class: "btn btn-default btn-xs glyphicon glyphicon-arrow-up invisible") %>
<% else %>
<%= link_to('', move_up_service_service_place_path(serviceplace, service_id: #service), class: "btn btn-default btn-xs glyphicon glyphicon-arrow-up") %>
<% end %>
<% if #service.last?(serviceplace) %>
<%= link_to('', move_down_service_service_place_path(serviceplace, service_id: #service), class: "btn btn-default btn-xs glyphicon glyphicon-arrow-down invisible") %>
<% else %>
<%= link_to('', move_down_service_service_place_path(serviceplace, service_id: #service), class: "btn btn-default btn-xs glyphicon glyphicon-arrow-down") %>
<% end %>
<% end %>
<!-- Unfortunately this is not very efficient way of showing the place because it calls the db for each item. -->
<%= Place.find(serviceplace.place_id).place_name %>
</dd>
<% end %>
I have a _user.html.erb partial that I am rendering on my Users#Index action with a form embedded to allow following and unfollowing of each user. I also have accompanying create.js.erb and destroy.js.erb files in my relationships controller to handle the AJAX. Right now I have to refresh the page in order to see the changes made by clicking the "follow/unfollow" button next to a users name except the first user's record behaves correctly.
_user.html.erb
<div id ="follow_form">
<% if current_user.following?(user) %>
<%= form_for(current_user.relationships.find_by(followed_id: user.id),
html: { method: :delete },
remote: true) do |f| %>
<%= f.submit "Unfollow", class: "btn btn-large" %>
<% end %>
<% else %>
<%= form_for(current_user.relationships.build(followed_id: user.id),
remote: true) do |f| %>
<div><%= f.hidden_field :followed_id %></div>
<%= f.submit "Follow", class: "btn btn-large btn-primary" %>
<% end %>
<% end %>
create.js.erb
$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>")
$("#followers").html('<%= #user.followers.count %>')
destroy.js.erb
$("#follow_form").html("<%= escape_javascript(render('users/follow')) %>")
$("#followers").html('<%= #user.followers.count %>')
here is a link to my users_controller.rb https://gist.github.com/anonymous/9608208 and my relationships_controller.rb https://gist.github.com/anonymous/9608284
I've tried changing the div to something dynamic like
<div id ="follow_form_<%= user.id %>">
But when I make the same change to the javascript like this
$("#follow_form_<%= user.id %>").find.html("<%= escape_javascript(render('users/unfollow')) %>")
it doesn't seem to work.
I've been trying to figure out how to pass the user.id attribute into the javascript but am falling flat.
It turns out the problem was in my _follow_form.html.erb partial.
I had to change
<div id="follow_form">
to
<div id="follow_form_<%= #user.id %>">
I thought I only needed to make the change in the _user.html.erb partial but I was wrong. My current user partial uses the same div id to contain the button in the Users#Index view
<div id ="follow_form_<%= user.id %>">
The intended URL is
"versions/compare/id1/id2"
Obviously, this doesn't work, but shows the intention
<%= form_tag("/versions/compare/", :method => "get") do %>
<% versions.each do |v| %>
<% v.id == #version.id ? current_class = " current" : "" %>
<li class="version-list <%= current_class %>">
<%= check_box_tag(:compare_version, v.id) %>
<%= link_to v.user.name, version_path(v) %>,
</li>
<% end %>
<%= submit_tag("Compare") %>
<% end #form tag
%>
The code above produces http://localhost:3000/versions/compare/?utf8=%E2%9C%93&compare_version=13&compare_version=12&commit=Compare
Is there a way to modify the form (+ JS?) so that it gets the desired URL?
"versions/compare/id1/id2"
you need to understand how form_tag work, it can not change the url after rendering the document.
If you want to change the url, use jquery to change it.
Or, just submit the form into some other url like
<%= form_tag("/comparing_version")
and then, in the controller, after processing everything, you redirect user to
"versions/compare/id1/id2"