In this article, we will create a Rails 7 Application and implement Infinite Auto Scroll. The scroll will be done using Hotwire Turbo only, and not a single line of Javascript will be used.
The App will display ten posts by default and on reaching the end of the page, more posts in batches of ten will be introduced further until there are no more posts.
Create a Rails 7 Application
We get started by creating a Rails 7 Application.
rails _7.0.3_ new infinite-scroll-turbo
Add a Model
We are adding a simple model with only a body attribute, to keep things simple.
rails g scaffold Post body:text
rails db:migrate
Set the Posts page as the Home Page
Change the default root
to Posts#index
in config\routes.rb
root "posts#index"
Add Simple CSS
Adding simple CSS makes the application more presentable.
<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
Adding a Turbo Form to create some Post Entries
We will add the Post Form partial created by the Scaffold command to the index page.
<div id="new_post">
<%= render partial: "posts/form", locals: { post: Post.new } %>
</div>
This makes it more convenient
to add more Posts
Adding a Turbo Frame to list Posts through a Turbo Stream
We add the following turbo_frame
to app/views/posts/index.html.erb
<div id="posts">
<%= turbo_frame_tag "posts", src: posts_path(format: :turbo_stream), loading: :lazy %>
</div>
and the corresponding Index code
for turbo in a file app/views/posts/index.turbo_stream.erb
<%= turbo_stream.append "posts" do %>
<%= render partial: "posts/post", collection: @posts %>
<% end %>
Instead of using index.turbo_stream.erb
,
the same can be achieved by adding a respond_to
format
in the turbo_stream
method.
Posts
turbo frame
and Posts#index
turbo stream
illustrate the magic of Turbo / Rails7.
What we are doing is dynamically loading content from the Posts#index
page,
as the DOM encounters the Posts
turbo_frame
without writing any JavaScript code.
Adding Pagination
To get the load more functioning,
we need to implement pagination functionality.
We can change the Posts#index
to the following
def index
page_limit = 10
@current_page = params[:page].to_i
@posts = Post.offset(page_limit*@current_page).limit(page_limit)
@next_page = @current_page + 1 if(Post.all.count > page_limit*@current_page + page_limit)
end
The above code will return @posts
based on the params[:page]
in groups of 10, which can be set by page_limit
Additionally, @next_page
and @current_page
variables are set to indicate if there is a next page available,
and the value of the current page.
Dynamically loading Posts with TurboFrames
As illustrated in the previous section we can call a get method through a turbo stream using Turbo_frame, we will use this approach to append pages to a frame until there are no more posts.
We can change the app/posts/index.html.erb
code to the following code
<div id="posts">
</div>
<%= turbo_frame_tag "load_more", src: posts_path(format: :turbo_stream), loading: :lazy %>
and app/posts/index.turbo_stream.erb
as follows:
<%= turbo_stream.append "posts" do %>
<%= render partial: "posts/post", collection: @posts %>
<% end %>
<% if @next_page.present? %>
<%= turbo_stream.replace "load_more" do %>
<%= turbo_frame_tag "load_more", src: posts_path(page: @next_page, format: :turbo_stream), loading: :lazy %>
<% end %>
<% end %>
We have an empty div#posts
and a frame after that which recursively
lazy loads more posts until there are none.
As soon as the end of the turbo_frame_tag#load_more
appears,
it will call the index.turbo_stream
which will append
the paginated posts to div#posts
and replace the div#load_more
with a fresh turbo_frame_tag
of the @next_page
Conclusion
Turbo extends the Rails Frameworks to make Responsive Applications with minimal code.