Skip to content

Instantly share code, notes, and snippets.

@kohheepeace
Last active June 20, 2021 01:03
Show Gist options
  • Save kohheepeace/411aad902112323f4088f40ad65b445e to your computer and use it in GitHub Desktop.
Save kohheepeace/411aad902112323f4088f40ad65b445e to your computer and use it in GitHub Desktop.
Rails kaminari infinite scroll with pure(vanilla) js

Rails kaminari infinite scroll with pure(vanilla) js

Imgur

I wrote this gist because the official infinite scroll tutorial for the kaminari was outdated.

📌Create Rails App

Start by creating a new rails application.

rails new infinite_kaminari
cd endless_kaminari

📌Install Dependencies

Add 'kaminari' to your Gemfile and run the bundle command.

gem 'kaminari'
bundle

📌Creating Post Model

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

📌Prepare Dummy Data

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

📌Controller

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

# ...

📌Views

Extract Partial

  1. Make views/posts/_post.html.erb
  2. 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>

Modify posts/index.html.erb

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 %>

📌JavaScript

Add scroll listener to call

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 to append posts

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")%>");

Refs

Note about onscroll

Virtual Infinite Scroll (Todo)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment