I wrote this gist because the official infinite scroll tutorial for the kaminari was outdated.
Start by creating a new rails application.
rails new infinite_kaminari
cd endless_kaminari
Add 'kaminari' to your Gemfile and run the bundle
command.
gem 'kaminari'
bundle
Let's create a model, which will be used to showcase the infinite scrolling and migrate the database.
rails generate scaffold Post title:string author:string body:text
rails db:migrate
In db/seeds.rb
require 'securerandom'
75.times do
Post.create(
:title => "My Post #{SecureRandom.hex(2)}",
:author => SecureRandom.hex(6),
:body => SecureRandom.hex(32)
)
end
Then
rails db:seed
Add kaminari code: .page(params[:page])
and rails .order(:created_at)
in def index
action.
If there is no next page, @posts.next_page
will return nil
.
In app/controllers/posts_controller.rb
# ...
# GET /posts
# GET /posts.json
def index
@posts = Post.order(:created_at).page(params[:page])
@next_page = @posts.next_page
@total_pages = @posts.total_pages
end
# ...
- Make
views/posts/_post.html.erb
- Extract code from
views/posts/index.html.erb
views/posts/_post.html.erb
<tr>
<td><%= post.title %></td>
<td><%= post.author %></td>
<td><%= post.body %></td>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
views/posts/_post.html.erb
<p id="notice"><%= notice %></p>
<h1>Posts</h1>
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>Body</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody id="posts" data-next-page="<%= @next_page %>" data-total-pages="<%= @total_pages %>"> <%# 👈 Add id="posts", data-next-page and data-total-pages %>
<% @posts.each do |post| %>
<%= render partial: "posts/post", locals: { post: post } %> <%# 👈 render extracted partial %>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Post', new_post_path %>
import Rails from "@rails/ujs";
import Turbolinks from "turbolinks";
import * as ActiveStorage from "@rails/activestorage";
import "channels";
Rails.start();
Turbolinks.start();
ActiveStorage.start();
document.addEventListener("turbolinks:load", function () {
const postsDiv = document.getElementById("posts");
const totalPages = postsDiv.dataset.totalPages;
let nextPage = postsDiv.dataset.nextPage;
let isFetching = false;
let isFinish = false;
function loadMorePosts() {
if (isFinish || isFetching) return;
isFetching = true;
// 👇 Call `posts/index.js.erb` to fetch and attach next page posts
Rails.ajax({
url: "/posts.js",
type: "get",
dataType: "script",
data: `page=${nextPage}`,
success: function (data) {
isFetching = false;
nextPage++;
if (nextPage > totalPages) {
isFinish = true;
window.removeEventListener("scroll", scrollListener);
} else {
postsDiv.dataset.nextPage = nextPage;
}
},
});
}
function scrollListener(event) {
const customOffset = 0;
// 👇 Trigger loadMorePosts() when the scroll position reaches the bottom. https://stackoverflow.com/questions/9439725/javascript-how-to-detect-if-browser-window-is-scrolled-to-bottom#answer-40370876
if (
window.innerHeight + window.scrollY + customOffset >=
document.body.scrollHeight
) {
loadMorePosts();
}
}
window.addEventListener("scroll", scrollListener); /* 👈 Add scrollListener */
});
views/posts/index.js.erb
var postsDiv = document.getElementById("posts");
<%# https://stackoverflow.com/questions/12188381/render-partial-collection-array-specify-variable-name %>
postsDiv.insertAdjacentHTML("beforeend", "<%= j render(partial: "posts/post", collection: @posts, as: "post")%>");