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.
Related
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.
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 } %>");
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.
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 %>
My model has Chords, which have a many-to-many relationship with Notes through Chordnotes. My chords/show looks like this:
<% #chord.notes.each do |note| %>
<li id="NoteID<%= note.id %>" >
<span class="content"><%= note.name %> (<%= note.description %>) (ID: <%= note.id %>)</span>
<span class="timestamp">
Created <%= time_ago_in_words(note.created_at) %> ago.
</span>
<span class="content">
<%= link_to "remove (not working)", '#' %>
</span>
</li>
<% end %>
I want the link_to tag to remove, not the note, but the Chordnote that links the Note to the Chord. I was trying to do this by referencing the #id in the < li > tag, which is the ID of the Note, and passing the Note ID and the Chord ID from the /show page the user was on. My Chordnote controller looks like this:
def destroy
#chord = Chord.find(chord-id) <-- how do I get this chord id??
#note = Note.find(note-id) <-- how do I get this note id??
Chordnote.find_by(note_id: #note.id, chord_id: #chord.id).destroy
redirect_to chord_path(#chord)
end
This destroy action works when I hardcode chord-id and note-id with ID #'s, but I can't figure out how to pass the relevant Note and Chord IDs from the chords/show page.
Any help would be appreciated, thanks!
this is untested, mind you. But have you tried passing the relevant data through the link_to?
<% #chord.notes.each do |note| %>
<li id="NoteID<%= note.id %>" >
<span class="content"><%= note.name %> (<%= note.description %>) (ID: <%= note.id %>)</span>
<span class="timestamp">
Created <%= time_ago_in_words(note.created_at) %> ago.
</span>
<span class="content">
<%= link_to "remove (not working)", {controller: "whatever_controller", action: "destroy", chord_id: #chord.id, note_id: note.id } %>
</span>
</li>
<% end %>
and then you would access these in the destroy method like so
def destroy
#chord = Chord.find( params[:chord_id] )
#note = Note.find( params[:note_id] ) <-- how do I get this note id??
Chordnote.find_by(note_id: #note.id, chord_id: #chord.id).destroy
redirect_to chord_path(#chord)
end