GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — GitHub - ntamvl/InfiniteScrollStimulusExample: Infinite Scroll with Stimulus Example

BranchesTags
Open more actions menu

Latest commit

 

History

23 Commits
23 Commits

Folders and files

NameName
Last commit message
Last commit date
app
app
 
 
bin
bin
 
 
config
config
 
 
db
db
 
 
lib
lib
 
 
log
log
 
 
public
public
 
 
storage
storage
 
 
test
test
 
 
tmp
tmp
 
 
vendor
vendor
 
 
.dockerignore
.dockerignore
 
 
.gitattributes
.gitattributes
 
 
.gitignore
.gitignore
 
 
.node-version
.node-version
 
 
.ruby-version
.ruby-version
 
 
Dockerfile
Dockerfile
 
 
Gemfile
Gemfile
 
 
Gemfile.lock
Gemfile.lock
 
 
LICENSE
LICENSE
 
 
Procfile.dev
Procfile.dev
 
 
README.md
README.md
 
 
Rakefile
Rakefile
 
 
config.ru
config.ru
 
 
package.json
package.json
 
 
yarn.lock
yarn.lock
 
 

Repository files navigation

Infinite Scroll with Stimulus

Infinite scroll is a pagination mechanism where whenever the user reaches the end of the scroll area more content is loaded till there are no more content to load.

Introduction

Hotwire is a new set of tools extracted from Hey by Basecamp. It uses Asynchronous HTML and HTTP (also known as AHAH) to render partial updates to the DOM without full browser reload. You build your servers with any language of your choice and let Turbo handle the partial updates for you. Which makes your application to have a speed of an SPA while having the benefits of server-rendered partials.

HOTWire is not a single tool, but three tools that allow you to build super fast apps while not having to write tons of client-side JavaScript to manage the updates. The tools within HOTWire are

1- Turbo: which is responsible for the navigation in your application and rendering the server responses to update the correct partial in the DOM.

2- Stimulus: Sometimes we would like to add a little bit of client-side behaviour to our site, the feature is too simple to let Turbo manage it and doesn't require a round trip to the server. There, Stimulus comes into play. You add behaviour to your HTML and sprinkles of JavaScript for this.

3- Strada: Standardizes the way that web and native parts of a mobile hybrid application talk to each other via HTML bridge attributes

References:

  • https://stimulus.hotwired.dev/

Setup Rails project

rails new InfiniteScrollStimulusExample  -c=bootstrap -j=esbuild

cd InfiniteScrollStimulusExample
bundle add kaminari faker
yarn add @rails/request.js

rails g scaffold Post title body:text

Modify action index in app/controllers/posts_controller.rb

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  ...

  def index
    @page = params[:page] || 1
    @posts = Post.page @page
  end

  ...
end

Create infinitive_pagination stimulus controller

rails g stimulus infinitive_pagination
// infinitive_pagination_controller.js
import { Controller } from "@hotwired/stimulus"
import { get } from "@rails/request.js"

// Connects to data-controller="infinitive-pagination"
export default class extends Controller {
  static targets = ['lastPage', 'loadMoreButton']

  static values = {
    url: String,
    page: Number,
  }

  initialize() {
    this.handleScroll = this.handleScroll.bind(this)
    this.pageValue = this.pageValue || 1
    this.loading = false
  }

  connect() {
    window.loadMoreButtonTarget = this.loadMoreButtonTarget
    window.addEventListener("scroll", this.handleScroll)
  }

  disconnect() {
    window.removeEventListener("scroll", this.handleScroll)
  }

  handleScroll() {
    const reachEndPage = this.hasReachEndPage()
    if (reachEndPage && !this.hasLastPageTarget) {
      this.loadMore()
    } else {
      this.hideLoadMoreButton()
    }
  }

  hasReachEndPage() {
    const bottomHeight = 20
    let body = document.body, html = document.documentElement
    let height = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight)
    const distance = height - window.innerHeight - bottomHeight
    const reachEndPage = window.scrollY >= distance
    return reachEndPage
  }

  hasReachEndPage2() {
    const bottomHeight = 20
    const { scrollHeight, scrollTop, clientHeight } = document.documentElement
    const distance = scrollHeight - scrollTop - clientHeight
    return distance < bottomHeight
  }

  async loadMore() {
    if (this.loading) {
      return
    }

    this.loading = true
    this.pageValue += 1
    const url = new URL(this.urlValue)
    const currentSearchParams = new URLSearchParams(window.location.search)
    for (const [key, value] of currentSearchParams) {
      url.searchParams.set(key, value)
    }
    url.searchParams.set('page', this.pageValue)
    await get(url.toString(), { responseKind: 'turbo-stream' })
    this.loading = false
  }

  async handleLoadMoreButton(e) {
    await this.loadMore()
    e.target.blur()
  }

  hideLoadMoreButton() {
    this.loadMoreButtonTarget.classList.add('d-none')
  }
}

Modify index.html.erb

<p style="color: green"><%= notice %></p>
<h1>Posts</h1>
<%= link_to "New post", new_post_path %>
<div data-controller="infinitive-pagination"
  data-infinitive-pagination-url-value="<%= posts_url %>"
  data-infinitive-pagination-page-value="1"
>
  <div id="posts">
    <% @posts.each do |post| %>
      <%= render post %>
      <p>
        <%= link_to "Show this post", post %>
      </p>
    <% end %>
  </div>
  <button data-action="click->infinitive-pagination#handleLoadMoreButton" data-infinitive-pagination-target="loadMoreButton">
    Load more
  </button>
</div>

Create index.turbo_stream.erb

<%= turbo_stream.append "posts" do %>
  <%= render @posts %>
  <% if @posts.page(@page.to_i + 1).out_of_range? %>
    <span class="hidden" data-infinitive-pagination-target="lastPage"></span>
  <% end %>
<% end %>

Modify db/seeds.rb

# db/seeds.rb
500.times do
  Post.create title: Faker::Movie.title, body: Faker::Quote.famous_last_words
end

Migrate database and seed data

rails db:migrate db:seed

Currently I am running many apps with many different ports, while the Rails app will run on the default port 3000, so I need to update the Procfile.dev file to run on another port, here I will use the port 4001 to avoid conflicts, like the Procfile.dev content below::

web: env RUBY_DEBUG_OPEN=true bin/rails server -p 4001
js: yarn build --watch
css: yarn watch:css

Run app

./bin/dev

Open your browser and goto http://localhost:4001/posts

Enjoy!!! 😄

If you have any questions, please do not hesitate to contact me via X (Twitter) @nguyentamvn or Facebook @nguyentamvinhlong

About

Infinite Scroll with Stimulus Example

Topics

hotwire-stimulusrailsstimulus

Resources

Readme
MIT license
Activity

Stars

6 stars

Watchers

1 watching

Forks

0 forks
Report repository

Releases

Packages

Contributors

Languages

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.