Rails 4: changing link style upon Ajax call - javascript

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.

Related

Rails 4 x AJAX: partial not reloading on click with remote: true

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.

Rendering a partial to an edit form inside a popover in rails 4

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 %>

Multiple AJAX forms in the same view in Rails

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

How to make items on a rails form sortable

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 %>

Follow button using AJAX only updates first div

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 %>">

Categories