I am trying to apply AJAX on a toggle "Like" button. This setup updates the button status without refreshing the entire page, but the former <%= pluralize(#company.get_likes.size, "people liked it") %> is not removed ("0" people liked it, is added to the new "1" people liked it")
How can remove the previous "#company.get_likes.size" people liked it sentence?
How can I improve the code?
controller:
def toggle_favorite(company)
if user_signed_in?
if current_user.liked? company
link_to raw("<i class='fa fa-star'></i>"), unlike_company_path(company), remote: true, method: :put
else
link_to raw("<i class='far fa-star'></i>"), like_company_path(company), remote: true, method: :put
end
else
link_to 'sign in to like', new_user_session_path
end
end
route:
resources :companies do
member do
put "like" => "companies#like"
put "unlike", to: "companies#unlike"
end
end
view:
<div class="text-center">
<div id="<%= dom_id(company) %>">
<%= pluralize(company.get_likes.size, "people liked it")) %>
<%= toggle_favorite(company) %>
</div>
</div>
like.js.erb
let starIcon = document.querySelector("#company_<%= #company.id %>").querySelector('.fa-star')
starIcon.parentElement.outerHTML = "<%= pluralize(#company.get_likes.size, "people liked it") %> <%= escape_javascript(toggle_favorite(#company)) %>"
unlike.js.erb
let farstarIcon = document.querySelector("#company_<%= #company.id %>").querySelector('.fa-star')
farstarIcon.parentElement.outerHTML = " <%= pluralize(#company.get_likes.size, t('views.company.bookmark.count_bookmark')) %> <%= escape_javascript(toggle_favorite(#company)) %>"
You can achieve this from the application_helper.rb file:
For this particular case, I'd rather use .count instead of .size (It's up to you). For more info, you can check count vs length vs size in a collection Stack Overflow question.
1- Define method:
def pluralize_get_likes(get_likes)
if get_likes.count > 0
"#{get_likes.count} people liked this"
else
"Here the text you want to be displayed (e.g.: Be the first one to vote this)"
end
end
2- Apply method:
Replace "pluralize" with "pluralize_get_likes" in like.js.erb and unlike.js.erb files.
like.js.erb:
let starIcon = document.querySelector("#company_<%= #company.id %>").querySelector('.fa-star')
starIcon.parentElement.outerHTML = "<%= pluralize_get_likes(#company.get_likes) %> <%= escape_javascript(toggle_favorite(#company)) %>"
The same applies to the view:
<div class="text-center">
<div id="<%= dom_id(company) %>">
<%= pluralize_get_likes(company.get_likes) %>
<%= toggle_favorite(company) %>
</div>
</div>
That should do it.
Related
In my program one Board can have many Sections. On Boards#show I list all Sections for this Board. A user can create, edit and delete Sections for Boards this user has created.
I use Pundit for my authorizations.
My problem is that after having added AJAX to the create Section action, my policy checks don't work anymore. The AJAX call is working fine in that Sections are added but the "Edit" and "Delete" links are only displayed after I reload the page.
Boards#show
<% if policy(#board).edit? %>
<p><strong>Create a new Section</strong></p>
<%= render 'sections/shared/form', board: #board, section: #section %>
<% end %>
<br>
<p><strong>Sections</strong></p>
<div id="sections">
<% #board.sections.order(id: :asc).each_with_index do |section, index| %>
<span><%= "#{index + 1}. #{section.title}" %></span>
<% if policy(section).edit? %>
<%= link_to "Edit", edit_board_section_path(#board, #board.sections[index]) %>
<% end %>
<% if policy(section).destroy? %>
<%= link_to("Delete", board_section_path(#board, #board.sections[index]), method: :delete) %>
<% end %>
<br>
<% end %>
</div>
Form partial
<%= simple_form_for([#board, #section], remote: true, authenticity_token: true) do |f| %>
<%= f.input :title, id: "section_title" %>
<%= f.submit "Save" %>
<% end %>
AJAX call
var newSection = "<span><%= "#{#board.sections.length}. #{#section.title}" %></span><br>";
var sections = document.getElementById('sections');
sections.insertAdjacentHTML('beforeend', newSection);
var sectionTitle = document.getElementById('section_title');
sectionTitle.value = '';
I think the problem is that the newly created Section isn't inserted into the loop and thus the policy cannot be checked. How could I refactor my code to solve this?
You should be able to render you HTML templates into your js.erb file. See the post from the DHH https://signalvnoise.com/posts/3697-server-generated-javascript-responses how to use this properly. It is an older post so maybe some things changed but you should know what to search for. But if it still works this way, you should be able to do something like this:
Extract section to a partial _section.html.erb
<span><%= "#{index + 1}. #{section.title}" %></span>
<% if policy(section).edit? %>
<%= link_to "Edit", edit_board_section_path(#board, #board.sections[index]) %>
<% end %>
<% if policy(section).destroy? %>
<%= link_to("Delete", board_section_path(#board, #board.sections[index]), method: :delete) %>
<% end %>
<br>
Use this partial also in your Boards#show file.
Render your partial in the js.erb file
var newSection = "<span><%=j render #section %></span><br>";
Something like this should work, but I don't use server generated javascripts so they could have changed some things. But hopefully it will help you at least with the direction.
I wish to reload a partial-form with a button(Add). I'm new and don't know how to simply display fields in partial like listings one under the other as many times Add button is clicked. I can't find a relevant example. all AJAX examples mention js.erb when object is saved in DB.
<div class="panel-body">
<%= render partial: "degrees/form", :locals => { :f => f } %>
<%= f.link_to (fa_icon 'plus').to_s + " Add Another Qualification ", render(:partial => 'degrees/form', :locals => { :f => f }), class: "btn btn-primary" %>
</div>
Here, #application is the main form trying to display degree fields. Partial is simply two text-fields- one for selecting educational degree and second its detail.Here's partial
<%= f.fields_for [Degree.new], :url => { :action => "index" } do |ff| %>
<div class = "form-group" }>
<%= ff.select :level, options_for_select(Job::EDUCATION, params[:level]), include_blank: "Select Degree", class: 'span-2' %>
<%= ff.text_field :description, :class => 'span5' %>
</div>
<% ff.submit "Add Another Degree", class: 'btn btn-primary' %>
You don't necessary need to save things to pass away to .js.erb... you can just instantiate...
But if your example is precise, you are missing the remote: true flag for the link... And the partial is not defined on the link... you need to make a view responding to the ajax...
Form example
<div class="panel-body">
<%= link_to new_thing_path, remote: true do %>
<i class="fa fa-plus">
Add another qualification
<% end %>
<div class="new-things-container">
</div>
</div>
Controller answering to the ajax request
class ThingsController < ApplicationController
def new
#thing = Thing.new
respond_with #thing
end
end
View for the ajax request rendering a partial inside a specified div
//views/things/new.js.erb
$('.panel-body .new-things-controller').append("<%= render partial: 'degrees/form', locals: { thing: #thing } %>")
I'm just getting started with the Braintree drop-in UI. Client side is javascript, server side is ruby on rails.
My system is very simple right now. The user is given a list of invoices that pertain to them. When the invoices have not yet been paid, they can click 'pay' and be taken to a new page that includes the Braintree Drop-In UI. My issue is this: When the user is taken to the 'pay' page, the drop-in UI does not appear. If the user reloads the page, the drop-in UI appears.
Why?
Here's some relevant code. It's pretty vanilla and not done yet - just roughed in.
From invoices/index.html.erb
<td>
<% if invoice.paid %>
<%= 'Paid' %>
<% else %>
<%= link_to 'Pay', payment_path(invoice.id) %>
<% end %>
</td>
From payments_controller:
class PaymentsController < ApplicationController
skip_before_action :verify_authenticity_token
before_action :set_invoice, only: [:pay, :checkout]
def pay
Braintree::Configuration.environment = :sandbox
Braintree::Configuration.merchant_id = 'merchant id'
Braintree::Configuration.public_key = 'public key'
Braintree::Configuration.private_key = 'private key'
gon.client_token = Braintree::ClientToken.generate()
end
def checkout
nonce = params[:payment_method_nonce]
result = Braintree::Transaction.sale :amount => #invoice.amount, :payment_method_nonce => "nonce-from-the-client"
if result.success?
message='Payment processed successfully. Thank you!'
#invoice.paid=true
#invoice.save
else
message='Unable to process payment. Reason = ' + result.message
end
redirect_to invoices_path, notice: message
end
private
def set_invoice
#invoice = Invoice.find(params[:id])
end
end
pay.html.erb:
<h1>Payments</h1>
<div>
<p>Invoice #: <%= #invoice.id %></p>
<p>Date: <%= #invoice.date %> </p>
<p>Description: <%= #invoice.description %> </p>
<p>Amount: <%= #invoice.amount %> </p>
</div>
<div class="form-container radius-box glassy-bg small-10 small-centered medium-8 large-6 columns">
<h2 class="mbs">New Transaction</h2>
<%= form_tag payments_checkout_path do%>
<%= hidden_field_tag 'id', #invoice.id %>
<p>Please enter your payment details:</p>
<div id="dropin"></div>
<%=submit_tag "Pay #{#invoice.amount}$", class: "button mt1" %>
<%end%>
</div>
and layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Actionable Software</title>
<%= include_gon %>
<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
<%= javascript_include_tag "application", "data-turbolinks-track" => true %>
<%= csrf_meta_tags %>
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
</head>
<body>
<%= render 'layouts/header' %>
<div class='container'>
<% flash.each do |name, msg| %>
<%= content_tag(:div, msg, class: "alert alert-info") %>
<% end %>
<%= yield %>
</div>
</body>
</html>
I'm going to guess the script that populates that element isn't written in a way that works with Turbolinks.
Try opting that page or section out of turbolinks. Or just disable it completely and see if it fixes it.
I ran into the same issue in my app.. change on your index page
<%= link_to 'Pay', payment_path(invoice.id) %>
to
<%= link_to 'Pay', payment_path(invoice.id), data: { no_turbolink: true } %>
This should force the pay page to load completeley when you click through.
I have the model Box, and each Box has many box_videos (another model). I want the user to be able to add box_videos to the box, so I created the following edit form for that (after creation of #box):
<%= form_tag "/box_videos", { method: :post, id: "new_box_videos", remote: true } do %>
<%= text_field_tag "box_videos[][link]", '' %>
<%= text_area_tag "box_videos[][description]", '' %>
<%= hidden_field_tag("box_videos[][box_id]", #box.id) %>
<%= hidden_field_tag("box_videos[][user_id]", current_user.id) %>
<div class="another_video">Add Another Video</div>
<%= submit_tag "Save Videos" %>
<% end %>
<%= form_for(#box) do |f| %>
<%= f.text_field :name %>
<%= f.text_field :size %>
<%= f.text_field :all_other_attributes %>
<%= f.submit "Create Box" %>
<% end %>
And some Javascript to facilitate adding more box_videos in one click.
<script>
$('.another_video').click(function() {
$('#new_box_videos').prepend('<input id="box_videos_link" name="box_videos[][link]" placeholder="Link to a youtube video." style="width: 18em;" type="text" value=""><textarea id="box_videos_description" name="box_videos[][description]" placeholder="Describe this video." style="width: 18em;"></textarea><br/><br/><input id="box_videos_box_id" name="box_videos[][box_id]" type="hidden" value="' + gon.box_id.toString() + '"><input id="box_videos_user_id" name="box_videos[][user_id]" type="hidden" value="' + gon.user_id.toString() + '">');
});
</script>
The above code works, in that params[:box_videos] when submitting three box_videos is as follows:
[{"link"=>"https://www.youtube.com/watch?feature=player_detailpage&v=dpAP8bq3ddU
", "description"=>"foo", "box_id"=>"63", "user_id"=>"16"}, {"link"=>"https
://www.youtube.com/watch?feature=player_detailpage&v=dpAP8bq3ddU", "description"
=>"bar", "box_id"=>"63", "user_id"=>"16"}, {"link"=>"https://www.you
tube.com/watch?feature=player_detailpage&v=dpAP8bq3ddU", "description"=>"hello world",
"box_id"=>"63", "user_id"=>"16"}]
In my controller I would just create a box_video object for every hash in the array and it works out just fine. BUT the problem comes when each time I submit the nested form_tag form, I send multiple requests to the controller action! Which means there are duplicates being created.
I can think of adding logic to the box_videos controller create action to check for duplicate content, but it seems rather hacky. Can anyone please let me know why this is happening?
You can't have nested form elements according to the html spec (see this answer).
You might want to use nested forms for this, it provides the creation of associated models via jquery with useful form_helper wrappers.
Right now I'm using AJAX for creating comments. However when I hit enter the textfield remains populated. I want the field to refresh (and still be able to write in another comment). no errors but it still doesn't refresh the textfield it seems. The create part works fine.
create.js/erb: (need to fix the second line here so that it fully replaces)
$('#<%= dom_id(#micropost) %>').html("<%= escape_javascript(render(:partial => #micropost.comments))%>")
$("#comment_field_<%=#micropost.id%>").replaceWith("<%= escape_javascript(render 'shared/comment_form', micropost: #micropost) %>")
Microposts/_micropost:
<span id="<%= dom_id(micropost) %>"><%= render micropost.comments %></span>
<span id="comment_field_<%=micropost.id%>"><%= render 'shared/comment_form', micropost: micropost if signed_in? %></span>
Shared/Comment_form:
<%= form_for #comment, id:"comment_form", remote: true do |f| %>
<%= hidden_field_tag :micropost_id, micropost.id %>
<div id="comment_field">
<%= link_to gravatar_for((current_user), size: 29) %>
<%= f.text_field :content, placeholder: "Say Something...", id: "comment_text_field", :style => "width: 508px; text-indent: 5px" %>
</div>
<% end %>
Needed to only use jquery to clear the text_field by setting empty value
create.js.erb
$("#comment_text_field_<%=#micropost.id%>").val("")
best practice to use a unique dynamic ID but probably would have worked either way.
Shared/Comment_form:
<%= f.text_field :content, id: "comment_text_field_#{micropost.id}" %>