Could anyone assist me in setting up my js.erb file to cover two scenarios.
At the moment i have infinite scroll working when the page initially loads, but when i change the params the new collection loads fine but when i scroll down the page to load the rest of the results i just get the next set of 6 objects replacing the last, rather than appending them onto the end
rehome.js.erb
$('.all_animals').append("<%= j render #animals %>");
<% if #animals.next_page %>
$('.pagination').replaceWith('<%= j will_paginate #animals %>');
<% else %>
$(window).off('scroll');
$('.pagination').empty();
<% end %>
Javascript
function infiniteScroll() {
if($('#infinite-scrolling').size() > 0) {
$(window).on('scroll', function(){
//Bail out right away if we're busy loading the next chunk
if(window.pagination_loading){
return;
}
more_url = $('.pagination a.next_page').attr('href');
if(more_url && $(window).scrollTop() > $(document).height() - $(window).height() - 50){
//Make a note that we're busy loading the next chunk.
window.pagination_loading = true;
$('.pagination').text('Loading.....');
$.getScript(more_url).always(function(){
window.pagination_loading = false;
});
}
});
}
}
So on page load i call the function
$(function(){
infiniteScroll();
});
and as a ajax success callback
success: function(data) {
infiniteScroll();
}
When the ajax call is made the params :rehomed are always present so i could wrap the separate logic in
<% if params[:rehomed].present? %>
<% end %>
does anyone have any ideas for dealing with the newly loaded content in my rehome.js.erb file
Thanks
Update
So on page load i need this to be the logic
$('.all_animals').append("<%= j render #animals %>");
<% if #animals.next_page %>
$('.pagination').replaceWith('<%= j will_paginate #animals %>');
<% else %>
$(window).off('scroll');
$('.pagination').empty();
<% end %>
and when an update is made to the collection i need
$('.all_animals').empty();
$('.all_animals').append("<%= j render #animals %>");
<% if #animals.next_page %>
$('.pagination').replaceWith('<%= j will_paginate #animals %>');
<% else %>
$(window).off('scroll');
$('.pagination').empty();
<% end %>
How to combine them into one statement is what im trying to work out
Related
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.
I'am using will_paginate gem to paginate my posts. And I want to do infinite scroll. I watch few video and read couple of posts, but thats not really what I need (because of my app structure). You see, home.html.erb (to be short), looks like:
<div class='news-lent'>
<%= render 'posts/posts_for_all', posts_variable: #posts %>
</div>
And my _posts_for_all.html.erb (to be short), look like (to be short):
<% posts_variable.each do |post| %>
<%= link_to post.theme, post %>
<% end %>
<% will_paginate posts_variable, :next_label => "More" %> # => i need it every
time the page is loaded
I've already wrote JS for all, except one line:
$('.news-lent').append('<%= j render *some_thing* %>')
Well, I don't know what pass to render. I cannot pass my #posts(it will return error(missing partial posts/_post)), and #posts.next_page(it will return 2), and #{posts_path(#post)}?page=#{#post.current_page+1}(but this don't work too) and other problems.
Can you help me, I need something to put inside j render so it rendered next page?
UPDATE
I reworked everything with RailCast videos, and same way like #Szilard Magyar tell me to do, but now it keep rendering the same (first page) all time and it not rendering second page at all, what can create such problem?
UPDATE
Follow railcast video (reworked my app structure), and a new problem appears:
My home.js.coffee:
jQuery ->
url = $('.pagination .next_page a').attr('href')
$(window).scroll ->
if url && $(window).scrollTop() > $(document).height() - $(window).height() - 50
$('.pagination').text("Fetching...")
$.getScript(url)
My index.js.erb:
$('.one').append('<%= j render #posts, posts_variable: #posts %>');
<% if #posts.next_page %>
$('.pagination').replaceWith('<%= j will_paginate(#posts) %>');
<% else %>
$('.pagination').remove();
<% end %>
<% sleep 3 %>
My index.html.erb:
<div class="news-lent">
<div class="one">
<%= render #posts, posts_variable: #posts %>
</div>
<div id="infinite-product-scrolling">
<%= will_paginate #posts, :next_label => " Еще" %>
</div>
</div>
But now, when I'am on first page and scroll down it rendering second page, but 2x times, and when I again scrolling to bottom and it rendering second page (it not rendering third page). I don't know how to fix it, can you land me again?
index.html.erb
<div class="product-index-list new-product-insert">
<%= render #products %>
</div>
<div id="infinite-product-scrolling">
<%= will_paginate #products %>
</div>
<div class="no-more">
<p>No more products.</p>
</div>
_product.html.erb
<div class="name"><%= product.name %></div>
<div class="oneliner"><%= product.oneliner %></div>
products.js
if ($('#infinite-product-scrolling').size() > 0) {
$(window).on('scroll', function() {
$('#infinite-product-scrolling').hide();
var more_products_url;
more_products_url = $('#infinite-product-scrolling .pagination .next_page a').attr('href');
if (more_products_url && $(window).scrollTop() > $(document).height() - $(window).height() - 60) {
$('.pagination').html("<i class='fa fa-circle-o-notch fa-2x fa-spin'></i>");
$('#infinite-product-scrolling').show();
$.getScript(more_products_url);
}
});
};
index.js.erb
$('.new-product-insert').append('<%= j render #products %>');
<% if #products.next_page %>
$('.pagination').replaceWith('<%= j will_paginate #products %>');
<% else %>
$(window).off('scroll');
$('.pagination').remove();
$('.no-more').delay(1000).fadeIn(1500);
<% end %>
Solve it, the problem appears because of my mistake. In here:
jQuery ->
url = $('.pagination .next_page a').attr('href')
$(window).scroll ->
url variable was assigned only one time, before scrolling and that was my mistake. So if url was save, so it always rendered second page and never third. We have to move our url lower, under .scroll event and it will always check, what page have been rendered. Like this:
jQuery ->
$(window).scroll ->
url = $('.pagination .next_page a').attr('href')
By the way, I will recommend to all (if they are using jQuery from RailsCast tutorial) to add some script to prevent multiple page load when user scroll
jQuery ->
$(window).scroll ->
url = $('.pagination .next_page a').attr('href')
return if(window.pagination_loading) # -> add this
if url && $(window).scrollTop() > $(document).height() - $(window).height() - 50
window.pagination_loading = true # -> and this
$('.pagination').text('Fetching products...')
$.getScript(url).always ->
window.pagination_loading = false # -> and this
And for prevent bugs etc. to url variable you need to add a, so it look like url = $('.pagination .next_page a').attr('href') and not like in RailsCast tutorial: url = $('.pagination .next_page').attr('href')
How can I update my view keeping all existing ajax and will_paginate functionality in place?
I have a page rehome.html.erb
<div id="options">Option Select Here</>
<div class="all_animals">
<%= render #animals %>
</div>
<% unless #animals.current_page == #animals.total_pages %>
<div id="infinite-scrolling">
<%= will_paginate #animals %>
</div>
<% end %>
// WILL_PAGINATE
<script type="text/javascript">
$(function(){
if($('#infinite-scrolling').size() > 0) {
$(window).on('scroll', function(){
//Bail out right away if we're busy loading the next chunk
if(window.pagination_loading){
return;
}
more_url = $('.pagination a.next_page').attr('href');
if(more_url && $(window).scrollTop() > $(document).height() - $(window).height() - 50){
//Make a note that we're busy loading the next chunk.
window.pagination_loading = true;
$('.pagination').text('Loading.....');
$.getScript(more_url).always(function(){
window.pagination_loading = false;
});
}
});
}
});
</script>
This will load all the #animals collection, paginating it to 6 per page, and when I scroll down the page another 6 are loaded etc etc.
Corresponding controller
class PublicController < ApplicationController
before_filter :default_animal, only: [:rehome]
def rehome
respond_to do |format|
format.html
format.js
end
end
private
def default_animal
#animals = Animal.animals_rehome.paginate(:page => params[:page], :per_page => 6)
end
end
rehome.js.erb
$('.all_animals').append('<%= j render #animals %>');
<% if #animals.next_page %>
$('.pagination').replaceWith('<%= j will_paginate #animals %>');
<% else %>
$(window).off('scroll');
$('.pagination').remove();
<% end %>
So when an option is selected from the dropdown an ajax post is made to create a new query which will return a new collection of #animals
$.ajax({
type: 'POST',
url: '/public/rehomed',
data: data_send,
success: function(data) {
//console.log(data);
}
});
Controller
def rehomed
# conditions logic
#animals = Animal.joins(:user).where(conditions).paginate(:page => params[:page], :per_page => 6)
respond_to do |format|
format.js {render json: #animals }
end
end
What I want to do is have the new collection loaded (paginated to 6 per page again) and when I scroll down only show the objects belonging to the new collection of #animals (if there are any).
At the moment the pagination links are not updated as when I scroll down the page the original collection is loaded.
Edit
So I have created a rehomed.js.erb file which is pretty much the same as my rehome.js.erb:
$('.all_animals').empty();
$('.all_animals').append('<%= j render #animals %>');
<% if #animals.next_page %>
$('.pagination').replaceWith('<%= j will_paginate #animals %>');
<% else %>
$(window).off('scroll');
$('.pagination').remove();
<% end %>
and within my rehomed action
respond_to do |format|
format.js
end
So the new collection of animals is loaded, the pagination links are recreated but using the rehomed url, example being:
Before
<a class="next_page" href="/public/rehome?page=2" rel="next">Next →</a>
After
<a class="next_page" href="/public/rehomed?page=2" rel="next">Next →</a>
So when I scroll down I just get the following as the links don't exist and getScript fails
$('.pagination').text('Loading.....');
Edit 2
I have implemented #japed's answer but now after the new collection is rendered the pagination will continue to render the whole collection of the db including repeating the ones that where selected for the new collection, it's repeating itself.
How can I ensure that the correct url is generated for my links?
Will paginate allows you to set the params hash so you should be able to change it to use your other controller action like this:
will_paginate(#animals, :params => { :controller => "public", :action => "rehomed" })
How can I render a `.each do block in a js partial in Rails?
In my messages.js.erb file, I currently have:
<% #conversations.each do |convo| %>
<%= j render "message_listing", convo: convo %>
<% end %>
$('#conversations-body-container').append(
// APPEND RESULTS OF PREVIOUS .EACH DO RENDER
);
How can I go about appending the first block of code in my HTML using j render?
You could do like below:
<% #conversations.each do |convo| %>
$('#conversations-body-container').append('<%= j render "message_listing", convo: convo %>');
<% end %>
Or
<% #conversations.each do |convo| %>
$('<%= j render "message_listing", convo: convo %>').appendTo('#conversations-body-container');
<% end %>
THIS IS THE SOLUTION
So i'm using will_paginate / Bootstrap Will Paginate with Endless Scrolling.
To get the Pagination working:
1.) In my Controller i updated my index action with
#clips = Clip.order("created_at desc").page(params[:page]).per_page(20)
2.) Edit my index view:
<%= will_paginate #clips%>
DONE
Pagination works just fine.
To Add Endless scrolling i did the same steps as in my previous Rails 3 App.
1.) Edit my clips.js.coffee
jQuery ->
$('#clips-masonry').imagesLoaded ->
$('#clips-masonry').masonry itemSelector: ".clips-masonry" # Thats my Masonry
if $('.pagination').length # Thats for the Endless Scrolling
$(window).scroll ->
url = $('.pagination .next_page a').attr('href')
if url && $(window).scrollTop() > $(document).height() - $(window).height() - 50
# What to do at the bottom of the page
$('.pagination').text("Fetching more Clips...")
$.getScript(url)
$(window).scroll()
2.) Create an index.js.erb with:
$boxes = $('<%= j render(#clips) %>')
$('#clips-masonry').append( $boxes ).imagesLoaded( function(){
$('#clips-masonry').masonry( 'reload');
});
<% if #clips.next_page %>
$('.pagination').replaceWith('<%= j will_paginate(#clips) %>');
<% else %>
$('.pagination').remove();
<% end %>
3.) Added format.js to my Controller index action
def index
#clips = Clip.order("created_at desc").page(params[:page]).per_page(12)
respond_to do |format|
format.html
format.js
end
end
4.) My _clip.html.erb is wrapped with the div
<div class="clip-box clips-masonry" data-no-turbolink>
Ok, i got it working with my Updated Question, everyone who stumbles upon this problem, This is the solution.
Incase someone prefers to use JS/JQUERY:
$(window).scroll(function() {
var url;
// Checks if products are currently being loaded
if (window.pagination_loading) {
return;
}
// Grabs the URL from the "next page" button
url = $('.pagination .next_page').attr('href')
// Chckes if you're n height from the bottom
if (url && $(window).scrollTop() > $(document).height() - $(window).height() - 50) {
// Variable to know that you are currently loading products
window.pagination_loading = true;
// Text while loading
$('.pagination').text('');
// Run the script
return $.getScript(url).always(function() {
return window.pagination_loading = false;
});
}
});
Index.js.erb:
$products = $('<%= j render(#products) %>')
$('#productsContainer').append( $products );
<% if #products.next_page %>
$('.pagination').replaceWith('<%= j will_paginate(#products) %>');
<% else %>
$('.pagination').remove();
<% end %>
index.html.erb
<div class="container">
<%= will_paginate #products %>
</div>
Controller:
respond_to do |format|
format.html
format.js
end