Rails 6: execute a JS function after rendering partial - javascript

Rails & Javascript beginner here,
On a training project, I made flash messages disappear after few seconds using JQuery. A visitor would send AJAX request to add a product to his cart, then a flash partial 'Added to cart' appears and automatically fades out after few seconds.
# application.html.erb
<div id='flash' class='flex-column'>
<%= render partial: 'shared/flash' %>
</div>
# shared/_flash.html.erb
<% flash.each do |key, value| %>
<%= display_flash(key, value) %>
<%= javascript_pack_tag 'custom/flash' %>
# this works, but injects the script each times the partial is rendered
<% end %>
# helpers/application_helper.rb
def display_flash(key, value)
def display_flash(key, value)
div_class = [flash_class(key), 'text-center fade show alert-dismissible'].join(' ')
content_tag(:div, class: div_class) do
content_tag(:p, value) +
button_tag(class: 'ml-auto close', 'data-dismiss': 'alert', type: 'button') do
content_tag(:span, '×'.html_safe, 'aria-hidden': 'true') +
content_tag(:span, 'Close', class: 'sr-only')
end
end
end
end
// app/javascript/packs/custom/flash.js
function closeFlash(){
let lastAlert = $('#flash .alert:last')
function fadeFlash() {
lastAlert.animate( {opacity: 0}, 2000, function() {
$(this).hide('slow', function() {
$(this).remove()
});
});
};
setTimeout(fadeFlash, 2000)
};
closeFlash();
The issue with this is that it pollutes my DOM with unnecessary <script> tags:
This could be fixed, but is there a suitable way to execute one specific javascript function after rendering a (AJAX) partial ?
In my case, executing closeFlash() located in packs/custom/flash.js each time a partial is rendered.
Thanks for your help and your time
EDIT Solution:
From Amit Patel answer and this post
# app/views/shared/_flash.html.erb
<% flash.each do |key, value| %>
<%= display_flash(key, value) %>
<script>
$(function() {
closeFlash();
});
</script>
<% end %>
// app/javascript/packs/custom/flash.js
window.closeFlash = function() {
let lastAlert = $('#flash .alert:last')
function fadeFlash() {
lastAlert.animate( {opacity: 0}, 2000, function() {
$(this).hide('slow', function() {
$(this).remove()
});
});
};
setTimeout(fadeFlash, 2000)
};
It doesn't inject the whole function but (I believe) the minimal javascript code to call it.

Move <%= javascript_pack_tag 'custom/flash' %> to your layout or your application.js` so that it is available across the app.
modify your template like
application.html.erb
<div id='flash' class='flex-column'>
<%= render partial: 'shared/flash' %>
<script>
$(function(){
closeFlash();
});
</script>
</div>

A more recent approach would be to set a Stimulus controller.

Related

Individual JavaScript for multiple records in Rails

I've been trying to wrap my head around this for days now, but I can't seem to find a solution for this problem.
Basically what I want to do is a feed similar to Facebook. A feed with multiple posts, of which each has comments/replies.
Now what I can't get to work is an AJAX "Load more" button for each post. I've only got it working for all posts at once, but not for individual ones.
What I have so far:
The feed:
<% #posts.each do |post| %>
<%= render "posts/thread", post: post %>
<% end %>
The threads:
<%= render post %>
<% replies = post.replies.paginate(:page => params["replies#{post.id.to_s}"], :per_page => 2) %>
<%= render replies %>
<%= will_paginate replies, :param_name => 'replies'+post.id.to_s %>
<%= link_to "Load More", "#", class: "load-more", id: post.id, :remote => true %>
The posts:
<div class="post" data-id="<%= post.id %>">
## content ##
</div>
pagination.coffee:
jQuery ->
$('.load-more').on 'click', ->
postId = $(this).attr('id')
more_posts_url = $('#post-'+postId+' .pagination .next_page').attr('href')
if more_posts_url
$('.load-more').html('<img src="/assets/ajax-loader.gif" alt="Loading..." title="Loading..." />')
$.getScript more_posts_url
return
return
index.js.erb:
<%= #posts.each do |post| %>
$('#post-<%= post.id %>').append('<%= j render post.replies %>');
<% end %>
But this does nothing.
I really don't understand how JS works for multiple records on one page.
I suppose this could help: https://jsfiddle.net/se708oou/2/
Main points of difference:
You should loop through all of the .load-more elements
$('.load-more').each(function() {
Change reference to use this
$(this).on('click', function() {
(Miscellaneous) This is how you take data-* in jQuery:
postId = $(this).data('id');
Cheers! :)
Your posts div
<div class="post" data-id="<%= post.id %>">
...
</div>
Has a class of post and a data attribute id, but in index.js.erb you are trying to select #post-<%= post.id %>
That selector isn't in the dom, You need to change your selector to
$('.post[data-id=<%= post.id %>').append('<%= j render post.replies %>');

Javascript not working when page is refreshed (Rails)

On Rails 4, I am trying to submit a form whenever I focus out the input field, without having to click on submit. When I place the script on the html page, it works fine:
<td>
<%= form_for commitment, html: {class:"index_edit_comments"}, remote: :true do |f| %>
<%= f.text_field :comments, class:"index_edit_comments_input" %>
<% end %>
<script>
$(document).ready(function() {
$(".index_edit_comments").each(function() {
$(this).find(".index_edit_comments_input").blur(function() {
$(this).submit();
});
});
});
</script>
</td>
But when I move the same code to a separated .js file, it is not functioning properly. It works right after loading the page, but when I use another function that refreshes the page, it stops working.
On the .js.coffee file it looks like this:
$(document).ready ->
$(".index_edit_comments").each ->
$(this).find(".index_edit_comments_input").blur ->
$(this).submit()
return
return
return
The other function that refreshes the page is this:
At index.html.erb:
<% if commitment.check == true %>
<td><%= link_to 'Yes', toggle_check_commitment_path(commitment), type: "button" %></td>
<% else %>
<td><%= link_to 'No', toggle_check_commitment_path(commitment), type: "button" %></td>
<% end %>
At controller:
def toggle_check
#c = Commitment.find(params[:id])
#c.toggle!(:check)
redirect_to commitments_path
end
Thanks for your help. I've did a lot of research and was not able to figure this out by myself.
After page refresh DOM has change, so your cod will not work.
Try:
$('html').find('.index_edit_comments').each(function() {
$(this).find(".index_edit_comments_input").blur(function() {
$(this).submit();
});
});
If refresh change all DOM html, my code example will not help.

rails crud using jquery popover via bootstrap

I tried to add jquery popover to rails default cruds instead of redireting to views and what I am doing is rendering the form in the jquery popover:
<%= content_tag_for :tr, #order.order_items do |i| %>
<a class='btn btn-mini label-with-popover' id=<%=i.id%>><%= t('helpers.links.edit_html') %> </a>
<% end %>
<script type="text/javascript">
$(function () {
$('.label-with-popover').popover({
html : true,
content: "<%= escape_javascript(render :partial => 'form_item') %>" ,
placement: 'top'
} );
});
</script>
and here is my form_item:
<%= simple_form_for :order_item, url: admin_shop_order_path, :html => { :class => 'form-horizontal' } do |f| %>
<div class='form-inputs'>
<%= f.input :item_id , :collection => #shop.products.collect{|b| b.variants}.flatten.map{|variant| ["#{variant_full_title(variant)}", variant.id]}%>
<%= f.input :quantity %>
</div>
<div class="form-actions">
<%= f.button :submit, :class => 'btn-primary' %>
<%= link_to t("helpers.links.cancel"), admin_shop_orders_path, :class => 'btn' %>
</div>
<% end %>
but the problem appears on the edit button. to render the edit form we need the object that we want to edit(:order_item I mean) and a way to get that is by using the id and this is why I have set the anchor tag id. now we have to get that anchor id within the popover function but $(this).id doesn't work. any suggestion?
In jQuery, you need to use attr('id') to get the id of an element. Try to replace $(this).id with:
$(this).attr('id')
See jQuery's documentation for more details: http://api.jquery.com/attr/
But a prefered way to achieve this is usually to use data attributes. It's a clean way to pass data from your view to some JS code. Your link would look like this:
<a class='btn btn-mini label-with-popover' data-item-id=<%=i.id%>>...</a>
And in your JS file, you would get this value using:
$(this).data('item-id')
jQuery's documentation: http://api.jquery.com/data/
You are rendering wrongly, I think, you should try to render your partial with locals. You can also pass local variables into partials, making them even more powerful and flexible.
In your case, while rendering your partial form_item in script tag, so you can write it like this:
<script type="text/javascript">
$(function () {
$('.label-with-popover').popover({
html : true,
content: "<%= escape_javascript(render :partial => 'form_item', :locals => {order_item: #order_item}) %>" ,
placement: 'top'
} );
});
</script>
and in your form you will be able to access it like :
<%= form_for(order_item) do %>
# write your form stuff %>
<% end %>
This way you can handle your form for both create or edit operations.
First I suggest you to pass id with some text added to it(I replaced the id with "link_<%= i.id%>").
Second call a onclick function on your a tag:
<%= content_tag_for :tr, #order.order_items do |i| %>
<a class='btn btn-mini label-with-popover' id="link_<%=i.id%>" ><%= t('helpers.links.edit_html') onclick="javascript:openPopup(this.id)" %> </a>
<% end %>
and Last but not the least, get id in your function, and pass it through your partial.
<script type="text/javascript">
function openPopup(a_id) {
var id = a_id.split("link_")[1];
$('.label-with-popover').popover({
html : true,
content: "<%= escape_javascript(render :partial => 'form_item', locals => {:id => id}) %>" ,
placement: 'top'
} );
});
</script>
I am not good at javascript, but from my answer, you will get, what I wanted to explain you. and I am sure that you will find a more improved way to do this. And If you do, please post it here as well. Hope it will help. Thanks

JQuery: Run script on ajax added form elements

I have a form element which is being pulled in on request with ajax. I am then trying to perform an ajax request on the inserted text box to find a location as it is typed. The code works on the first textbox but simply fails when the second one is inserted. I've tried to get the script to reload itself when the ajax has completed but it still won't work. Help would be much appreciated.
Form.html.erb - Sets up the rails nested form and pulls in partial
<%= nested_form_for(#post, :html=> {:multipart => true, :class=> "new_blog_post", :id=> "new_blog_post"}) do |f| %>
...
<fieldset>
<%= render :partial => 'search_locations', :locals => { :f => f } %>
</fieldset>
<p><%= f.link_to_add "Add a location", :locations %></p>
...
<% end %>
partial.html.erb - Pulled in on page load and then when 'Add a location' button is pressed
<fieldset>
<%= f.fields_for :locations do |m| %>
<%= m.text_field :name ,:class=>"localename", :placeholder=> "Name of the location", :autocomplete => "off" %>
<%= m.text_field :longitude, :class => "long" %>
<%= m.text_field :latitude, :class => "lat" %>
<div class="latlong">
<p class="help-block">Enter the name of the town or city visited in this blog entry.</p>
</div>
<%= m.link_to_remove "Remove this location" %>
<% end %>
</fieldset>
Javascript (placed at bottom of form)
<script type="text/javascript">
function locationchecker() {
// Rails to multiply the script 20 times
<% (0..20).each do |i| %>
// when the #search field changes
$(".localename:eq(<%=i%>)").keyup(function() {
// get the value of searchfield
var location<%=i%> = $(".localename:eq(<%=i%>)").val();
//Take the value of the textbox and pull it from geocoder
$.get('/locations/location?location='+location<%=i%>,this.value, function(searchone<%=i%>) {
$(".latlong:eq(<%=i%>)").html(searchone<%=i%>);
})
// Upon complete run the script again
.complete(function(searchone<%=i%>) { locationchecker });
});
<%end%>
}
// load script on doc ready
$(document).ready(locationchecker);
</script>
Help would be great!
Thanks in advance,
James
you should use the .on()(jQuery api doc) method to attach your keyup event handler, like so :
$('#myForm').on('keyup','.localename',function() {
// stuff here
var location = $(this).val(); // this = element on which the event was triggered
});

jQuery and Rails

I'm writing a Rails app and need to plug in this little bit of jQuery code, but I don't really know how to get it to work. Here's my controller code:
class ChatroomController < ApplicationController
def send_data
#role = Role.find_by_id(session[:role_id])
render :juggernaut do |page|
page.insert_html :bottom, 'chat_data', "<b>#{#role.name}:</b> #{h params[:chat_input]}<br>"
end
render :nothing => true
end
end
and view code:
<h2>Chat</h2>
<html>
<head>
<%= javascript_include_tag :defaults, :juggernaut %>
<%= juggernaut %>
</head>
<body>
<div id='chat_data', class="chatbox">
</div>
<br>
<%= form_remote_tag(
:url => { :action => :send_data },
:complete => "$('chat_input').value = ''" ) %>
<%= text_area_tag( 'chat_input', '', { :rows => 3, :cols => 70, :id => 'chat_input'} ) %>
<%= submit_tag "Send" %>
</form>
</body>
</html>
Now, I need to make the chatroom always scroll down to the bottom when any user sends a new message. But also, when the current user has manually scrolled up, disable this sort of behaviour. I found some jQuery code here: Scrolling Overflowed DIVs with JavaScript
Now I don't know to get it to work. I pasted into application.js:
$("#chat_data").each( function()
{
var scrollHeight = Math.max(this.scrollHeight, this.clientHeight);
this.scrollTop = scrollHeight - this.clientHeight;
});
I've also added <%= javascript_include_tag 'jquery', 'application' %> to the head of my view.
But when my chatroom log fills up, the scrollbar appears but does not automatically move to the bottom as new messages come through.
The problem seems to be that the code you've inserted is only run once, at the beginning of the script.
I don't know a lot about jquery so this is just a general solution.
function sub(data) {
$('#chat_data').each( function () {
var s_top = this.scrollHeight - this.clientHeight;
var scl = this.scrollTop == s_top;
this.innerHTML += '<br/>' + data;
if ( scl ) this.scrollTop = s_top + this.clientHeight;
})
};
problem is that now when you receive new data from the server you must add it into the #chat_data by calling sub("text that goes into chat window").
you'll have to replace
page.insert_html ....
with something that sends the rjs to the client, like:
page.call( :sub, data)
hope it helps.

Categories