Ruby on Rails Like button with image instead of text - javascript

I am currently working with the Acts_As_Votable gem in Ruby to use a Like link on each of my posts so that users can vote. I found a great answer on here that helped be do exactly that but I'm trying to figure out how I can use a heart icon image instead of the Like/Dislike text. I want the image to update without refreshing, just like the text links do but can't figure out the logic. Here is the current code I am using:
controllers/prides_controller.rb
def like
#pride = Pride.find(params[:id])
#pride.liked_by current_user
if request.xhr?
render json: { count: #pride.get_likes.size, id: params[:id] }
else
redirect_to #pride
end
end
def dislike
#pride = Pride.find(params[:id])
#pride.disliked_by current_user
if request.xhr?
render json: { count: #pride.get_likes.size, id: params[:id] }
else
redirect_to #pride
end
end
prides/show.html.erb
<div class="single-heart-this">
<% if current_user.liked? #pride %>
<span class="heart-icon-loved">
<%= link_to "Dislike", dislike_pride_path(#pride), class: 'vote', method: :put, remote: true, data: { toggle_text: 'Like', toggle_href: like_pride_path(#pride), id: #pride.id } %></span>
<% else %>
<span class="heart-icon">
<%= link_to "Like", like_pride_path(#pride), class: 'vote', method: :put, remote: true, data: { toggle_text: 'Dislike', toggle_href: dislike_pride_path(#pride), id: #pride.id } %></span>
<% end %>
<span class="heart-no" data-id="<%= #pride.id %>"><%= #pride.get_likes.size %></span>
<% end %>
</div>
javascript/pride.js.erb
# Rails creates this event, when the link_to(remote: true)
# successfully executes
$(document).on 'ajax:success', 'a.vote', (status,data,xhr)->
# the `data` parameter is the decoded JSON object
$(".heart-no[data-id=#{data.id}]").text data.count
return
javascript/prides.js.coffee
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
$(document).on 'ajax:success', 'a.vote', (status,data,xhr)->
# update counter
$(".heart-no[data-id=#{data.id}]").text data.count
# toggle links
$("a.vote[data-id=#{data.id}]").each ->
$a = $(this)
href = $a.attr 'href'
text = '$a.text()'
$a.text($a.data('toggle-text')).attr 'href', $a.data('toggle-href')
$a.data('toggle-text', 'text').data 'toggle-href', href
return
return
Thanks for your help.

Try this?
Add: class: "Change_me" to your link
<%= link_to image_tag("Dislike.png"), dislike_pride_path(#pride), class: 'vote', method: :put, remote: true, data: { toggle_href: like_pride_path(#pride), id: #pride.id }, class: "change_me" %>
src: link_to image_tag with inner text or html in rails
In your coffeescript, change the image src
$(".change_me").attr 'src', '/like.png'
src: Rails 3.1 CoffeeScript/JQuery - How to change the source of an image in a div?

Related

Rails how to render two different modals for destroy action

I want to add confirmation modal when bank manager has to delete bank_employee without clients bank_employee.users = nil. If bank_employee has clients I want to render different modal - destroy_confirmation_modal. How to do it in a proper way? Where should I put if condition?
code snipped of
edit.html.erb
<div class="row">
<div class="col-sm-12 col-md-12 col-xs-12 text-center bank-employee__button-wrapper bank-employees-users-registration__registrations-submit--wrapper">
<%= t('.delete') %>
<%= f.submit t('.submit'), id: "formSubmit", class: "bank-employee__button bank-employee__button-submit"%>
</div>
</div>
<% end %> // this `end` comes from `form_for`
<%= button_to "", bank_employee_path(#bank_employee), method: :delete, form: {id: "bankEmployeeDestroyForm" }, class: "bank-employee__button-destroy" %>
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12 text-center">
<%= link_to bank_employees_path do %>
<span class="bank-employee__back-button">
<%= image_tag "icon_back.svg", alt: "Back icon", class: ""%>
<%= t('.back') %>
</span>
<% end %>
</div>
</div>
</div>
<%= render "destroy_confirmation_modal" %>
I don't think I should update my controller method but maybe I'm wrong?
controller.rb
def destroy
authorize current_bank_employee
#bank_employee = find_bank_employee(params[:id])
if #bank_employee.users.any? && associated_bank_employees.present?
reassign_users_and_notify_bank_employee
elsif #bank_employee.users.any?
render :edit
else
#bank_employee.destroy
render :destroy_notice, locals: { old_bank_employee: #bank_employee, assigned: false }
end
end
EDIT
my routes.rb
resources :bank_employees, except: [:show], concerns: [:with_datatable] do
member do
get :confirm
end
end
rails routes showed me this path as confirm_bank_employee so I've changed if condition as follow
<% if #bank_employee.users.empty? %>
<%= button_to "", confirm_bank_employee_path(#bank_employee), form: {id: "bankEmployeeDestroyForm" }, class: "bank-employee__button-destroy", remote: true %>
<% else %>
<%= button_to "", bank_employee_path(#bank_employee), method: :delete, form: {id: "bankEmployeeDestroyForm" }, class: "bank-employee__button-destroy" %>
<% end %>
You need a different approach.
<% if #bank_employee.clients.empty? %>
<%= button_to "", bank_employee_confirm_path(#bank_employee), form: {id: "bankEmployeeDestroyForm" }, class: "bank-employee__button-destroy", remote: true %>
<% else %>
<%= button_to "", bank_employee_path(#bank_employee), method: :delete, form: {id: "bankEmployeeDestroyForm" }, class: "bank-employee__button-destroy" %>
<% end %>
now you need two things:
inside routes.rb create a collection for your blank_employee_confirm_path:
whatever setup you have, i assume you have some kind of blank_employees resources path:
resources :blank_employees do
member do
get :confirm
end
end
now, inside your controller you need to add the method confirm:
def confirm
## do whatever you want do to here
render :js
end
this will then head over to confirm.js
create a confirm.js.erb file inside your blank_employees view folder. To confirm this is working, you can add a console.log('it works') in it.
Once you have confirmed that it is working you can add the javascript code to the confirm.js.erb file:
$('#modal-body').html('<%= escape_javascript(render :partial => 'shared/your_confirmation_modal'%>');
with this setup, you need in your edit.html.erb file a <div id="modal-body"></div> that will take the modal. Also notice that in my example the confirmation modal is stored in "views/shared/_your_confirmation_modal.html". Change the path to your setup or create the exact path in order to make this work!
Notice that the "confirm" path is for the blank_employees that have no clients. The button will be only rendered when there is no client. All the other logic you had before for the blank_employees with clients stay the same. You don't need to change there anything. If you had any logic inside there for blank_employees without any clients, move the code to the confirm method.
One more thing: Make sure to add to your destroy method a render :js as well, and inside destroy.js.erb add the same kind of logic like inside confirm.js.erb, beside that you want to render the modal for blank_employees with clients. Your destroy.js.erb file should look something like this:
$('#modal-destroy').html('<%= escape_javascript(render :partial => 'shared/your_destroy_modal'%>');
Very important: Just like with the first modal, add a <div id="modal-destroy"></div> to your edit.html.erb file, otherwise it wont render the modal.
If something is unclear, let me know!
Greetings!

Rails Ajax Jquery

I'm working on an dynamic edit of an article using rails. On each articles can be added paragraphs. I wanted to use Ajax with Jquery following the 136-jquery-ajax-revised tutorial from railscast. I created the template for the form and the new.js.erb response but I keep geting same error:
ParagraphsController#new is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: []
This is the link that request the new form from view/article/show
<%= link_to 'New Paragraph', new_article_paragraph_path(#article), remote: true, class: 'uk-button uk-button-primary' %>
view/paragraph/_form
<div class="article-form-container">
<%= form_with scope: :paragraph, url: article_paragraphs, remote: true do |f| %>
<%= f.text_field :title, class: 'uk-input', placeholder: 'Title of paragraph (optional, can be a subtitle of the article)' %>
<%= f.text_area :text, class: 'uk-textarea', placeholder: 'Content of paragraph' %>
<%= f.hidden_field :position, :value => 3 %>
<div class="submit-button">
<%= f.submit class: 'uk-button uk-button-primary' %>
</div>
<% end %>
</div>
routes
resources :articles, only: %i[show update destroy create]
resources :articles do
resources :paragraphs, only: %i[new create update destroy]
end
view/paragraphs/new.js.erb
$('div#article-control-panel').hide("fast", function () {
$('div#article-control-panel').after('<%= j render 'form' %>')
})
controllers/paragraphs_controller
class ParagraphsController < ApplicationController
def new
#paragraph = Paragraph.new
end
def create
#paragraph = Paragraph.new(paragraph_params)
#article = Article.find(params[:article_id])
if #article.user == current_user
#paragraph.article = #article
#paragraph.save
end
respond_to do |format|
format.html { redirect_to #article }
format.js
end
end
def paragraph_params
params.require(:paragraph).permit(:title, :text, :position)
end
end
Can't figure out what the problem is. The error happens in the article page, after I press the link button.
Edit
Strange thing is that if i try to change the url of orm in something like article_path it will work...
Your controller is missing a respond_to. Your new function should look like this:
def new
#paragraph = Paragraph.new
respond_to { |format| format.js }
end
This respond_to allows rails to call the relevant .js.erb file, in this instance your new.js.erb file.
Just keep in mind that when you want to perform an ajax call, you require these few elements:
A respond_to { |format| format.js } in your controller action
Your js.erb file needs to be the same name as your controller action (foo.js.erb)
A partial to render through your js.erb file, typically named the same as your js file. (_foo.js.erb)
If it is a link, you need remote: true. If it is a form and you're using rails 5, you could use form_with or simply include remote: true too.

How do I render a partial containing a form_for for an edit function?

I have a div in a parent view that renders a show partial. In this show partial, I have a button that should change the parent's partial to render the 'form' partial to edit the object, but I keep getting jammed up because it seems that the 'form' partial's form_for is missing the #project object.
How can I pass this object to the 'form' partial? Am I missing something else?
I am rather new to using AJAX. Happy to provide more information if you need.
The error I am getting in the server terminal is
ActionView::Template::Error (First argument in form cannot contain nil or be empty):
routes.rb
get 'switchProjectView', to: 'projects#switch_main_view'
resources :projects
projects_controller.rb
class ProjectsController < ApplicationController
def new
#project = Project.new
end
def create
#project = Project.new(project_params)
if #project.save
flash[:success] = "New Project Added"
redirect_to #project
else
flash[:danger] = "Project Not Added. Please Try Again"
end
end
def show
#project = Project.find(params[:id])
end
def index
#projects = Project.all
end
def update
#project = Project.find(params[:id])
if #project.update_attributes(project_params)
redirect_to #project
else
render 'edit'
end
end
def edit
end
def switch_main_view
respond_to do |format|
format.html
format.js
end
end
def project_params
params.require(:project).permit(:main_contact_name, :id, :main_contact_phone, :main_contact_email, :project_name)
end
end
show.html.erb
<div class="body">
<div class="center jumbotron heading-jumbo">
<h1><%= #project.project_name %></h1>
</div>
<div class="body-jumbo jumbotron" id='project-details'>
<%= render "projects/show" %>
</div>
</div>
switch_main_view.js.erb
$('#project-details').html("<%= j render :partial => 'projects/form' %>");
_form.html.erb
<%= form_for(#project) do |f| %>
<%= f.label :project_name %>
<%= f.text_field :project_name, class: 'form-control'%>
<%= f.label :main_contact_name %>
<%= f.text_field :main_contact_name, class: 'form-control'%>
<%= f.label :main_contact_phone %>
<%= f.text_field :main_contact_phone, class: 'form-control'%>
<%= f.label :main_contact_email %>
<%= f.text_field :main_contact_email, class: 'form-control'%>
<%= f.submit 'Save Project', class: 'btn btn-primary'%>
<% end %>
_show.html.erb
<div class='text-center center'>
<h3><strong>Project Information</strong></h2>
</div>
<h2>Start Date: Launch Date:</h1>
<span class='pull-right jumbo-btn-span'><%= link_to "Edit Project Information", switchProjectView_path(#project), remote: true, class:'btn btn-primary jumbo-btn' %></span>
<h2>Primary Conact's Name: <%= #project.main_contact_name %></h2>
<h2>Primary Conact's Phone: <%= #project.main_contact_phone %></h2>
<h2>Primary Conact's Email: <%= #project.main_contact_email %></h2>
Well, with each request, you need to re-initialize the variable in your controller method. In your case, you need to do the following:
def switch_main_view
#project = Project.new
respond_to do |format|
format.html
format.js
end
end
Doing so will enable you to get rid of the error: First argument in form cannot contain nil or be empty, but that won't enable you to do what you are actually trying to do.
When you use link_to, by default it goes for an HTML request as you can see in Rails logs, something like following will appear:
Processing by ProjectsController#switch_main_view as HTML
In order to get the JS response, you need to tell link_to that you are up for a JS response, not for an HTML response, and you can do by passing format: :js in link_to like:
<%= link_to "Edit Project Information", switchProjectView_path(#project, format: :js), remote: true, class:'btn btn-primary jumbo-btn' %></span>
remote: true will be there to make sure that you aren't reloading the contents of the page, rather you are trying to fetch something from the server in the background.
Your request "switchProjectView_path(#project)" only send object id to server, so you need to load the object to present it in view for response.
1. First the route should look like this so that, the url could contain object id:
get 'switchProjectView/:id', to: 'projects#switch_main_view', as:"switchProjectView"
2. You have to load the object in controller:
def switch_main_view
#project = Project.find(params[:id])
respond_to do |format|
format.html
format.js
end
end
Try passing the variable when calling the partial:
$('#project-details').html("<%= j render :partial => 'projects/form', locals: {project: #project} %>");

Rails destroy js.erb

Can you tell me please, how I could remove object before destroy method will be end. When I use next pattern, removing object happen when photo was delete, but it take 1 or 3 or more seconds.
_form(edit action)
<% listing.photos.each do |photo|%>
<%= image_tag photo.image.thumb, class: 'thumbnail', id: "test"%>
<%= link_to "remove", photo_path(photo),class: 'btn btn-primary', method: :delete, data: { confirm: 'Are you sure?' }, remote: true %>
destroy.js.erb
$('#test').remove();
How I can use this pattern
_form:
<div id="test_<%= photo.id %>">
<%= image_tag photo.image.thumb, class: 'thumbnail'%>
<%= link_to "remove", photo_path(photo),class: 'btn btn-primary', method: :delete, data: { confirm: 'Are you sure?' }, remote: true %>
Destroy.js.erb:
$('#edit_image_<%= #photo.id %>').remove();
If you want to remove the image from the DOM before its real destroying on server to avoid delay you can apply event.preventDefault() on the 'remove' button click.
This will allow you to rewrite the normal behavior of the 'remove' button.
Take a look at this example about performing some UI actions before the original event and then triggering it.
Also please notice that it's not a good idea to remove something from UI without being sure it's already removed in general. It's not clear enough for the user. So, maybe it would be better to hide the image at first and in case there will be a server error while destroying it you'll show it again and display some instructive message also.
UPD
Considering following markup
<div id="test_<%= photo.id %>">
<%= image_tag photo.image.thumb, class: 'thumbnail' %>
<%= link_to "remove", "#", class: 'remove btn btn-primary', data: { id: photo.id, url: photo_path(photo) } %>
</div>
another option is to rewrite remote: true with separate jQuery.ajax() call.
$('.btn.remove').click(function () {
var $target = $(this).parents('#test_' + $(this).data('id'));
$.ajax({
url: $(this).data('url'),
method: 'DELETE',
beforeSend: function() {
$target.hide() # hiding corresponding image
},
error: function () {
$target.show() # revert hiding on error
console.log("Sorry! Couldn't remove image.") # log message
}
})
})
There is a much cleaner way to do it without a js.erb template:
<div class="photo">
<%= image_tag photo.image.thumb, class: 'thumbnail'%>
<%= link_to "remove", photo_path(photo),class: 'destroy btn btn-primary', method: :delete, data: { remote: true, type: 'json', confirm: 'Are you sure?' } %>
<div>
Now just setup an ajax handler:
// app/assets/javascripts/photos.js
$(document).on('ajax:success', '.photo .destroy.btn', function(){
$(this).parent('.photo').remove();
});
And set your controller to return the correct response code.
class PhotosController
def destroy
#photo = Photo.find(params[:id])
#photo.destroy!
respond_to do |format|
format.json do
head :no_content
end
end
end
end
This keeps your client side logic in app/assets/javascripts where it can be cached and minified instead of spreading it around in a bunch of glorified script tags.

Trying to destroy request via ajax for relationship entities in ruby on rails

I'm trying to delete a "budget"(in portuguese, "orçamento") via ajax request, and that budgets have a relationship belongs to cadastre and cadastre has many budgets. My components are as follows:
orcamentos_controller.rb
.
.
.
def destroy
#cadastro = Cadastro.find(params[:cadastro_id])
#orcamento = #cadastro.orcamentos.find(params[:orcamento_id])
#orcamento.destroy
respond_to do |format|
format.js { head :ok }
end
end
def index
#cadastro = Cadastro.find(params[:cadastro_id])
#orcamentos = #cadastro.orcamentos.paginate(page: params[:page], per_page: 10)
end
.
.
.
index.html.erb
... <%= render "orcamentos_lista" %> ...
_orcamentos_lista.html.erb
<table class="table">
<% #orcamentos.each do |orcamento| %>
<tr id="remove_orcamento_<%= orcamento.id %>">
<td>
<%= link_to "Orçamento nº #{orcamento.id}, registrado em #{orcamento.created_at.strftime("%d/%m/%Y")}", "#", class: "list-group-item", type: 'button'%>
</td>
<td>
<%= link_to "Excluir", orcamento, method: :destroy, remote: true %>
</td>
</tr>
<% end %>
</table>
<% content_for :js do %>
<% #orcamentos.each do |orcamento| %>
$('#remove_orcamento_<%= orcamento.id %>').bind('ajax:success',
function(){
$('#orcamento_<%=orcamento.id%>').remove();
}
);
<% end %>
<% end %>
routes.rb
.
.
.
resources :cadastros do
resources :orcamentos
end
.
.
.
I get the error undefined method orcamento_path for #<#<Class:0x007f5f39757930>:0x007f5f39cf1eb8> this line <%= link_to "Excluir", orcamento, method: :destroy, remote: true %>
I am a beginner in both ruby as on rails, so forgive me any ugliness in code.
run
rake routes
Then whatever path you want that link to have in it find it in the table of routes and use the 'prefix' for it with "_path" at the end. So if the resource you want is on that table and has a prefix of foo_bar then your link_to should have foo_bar_path used in your view.
so change this line
<%= link_to "Excluir", orcamento, method: :destroy, remote: true %>
to this
<%= link_to "Excluir", foo_bar_path, method: :destroy, remote: true %>
The problem you have is that you're calling orcamento as the link, when you've nested it within cadastros:
#config/routes.rb
resources :cadastros do
resources :orcamentos # -> url.com/cadastros/:catastros_id/orcamentos/:id
end
This means if you want to call the path helper for an orcamentos, you have to use the nested resource:
cadastros_orcamentos_path(#cadastros, orcamento)
If you're using link_to, you should be able to pass an array of the nested objects as you require:
link_to "Delete", [#cadastros, orcamento], method: :delete

Categories

Resources