レールと刺激による無限のスクロール・ページネーション


この記事では、いくつかの行のコードのみを使用して、無限のスクロールページシステムを構築する方法を学びます.我々は非常に単純なレールのアプリケーションを作成し、あなたのアプリケーションのすべてのリソースをPaginateに再利用することができます刺激コントローラで無限スクロール機能を実装します.我々はステップでこのステップを行うので、始めましょう!

Railsアプリケーションの作成
次のようにします.
rails new infinite-scroll-article --webpack=stimulus
JavaScriptなしで動作するページ付け機能を構築します.まずモデルを作りましょうArticle 文字列のタイトルとテキストの内容です.
rails g model Article title content:text
rails db:migrate
我々が我々を持つ今Article モデルは、Paginateに私たちのために100の記事を作成する種をつくりましょう.
# db/seeds.rb

puts "Remove existing articles"
Article.destroy_all

puts "Create new articles"
100.times do |number|
  Article.create!(
    title: "Title #{number}",
    content: "This is the body of the article number #{number}"
  )
end
データベース内の100個の記事を永続化するには、次のコマンドを実行します.
rails db:seed
私たちはモデルの部分に行くのが良いです、今では#index そして、それらの100条を表示するための対応するビュー.
rails g controller articles index
ルートファイルで、記事リストをホームページにしましょう.
# config/routes.rb

Rails.application.routes.draw do
  root "articles#index"
end
コントローラのすべての記事をデータベースから検索します.
# app/controllers/articles_controller.rb

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end
end
最後に、ビュー内のすべての100記事を表示しましょう.
<!-- app/views/articles/index.html.erb -->

<h1>Articles#index</h1>

<% @articles.each do |article| %>
  <article>
    <h2><%= article.title %></h2>
    <p><%= article.content %></p>
  </article>
<% end %>
これでローカルサーバーを起動できますrails s とwebpackサーバwebpack-dev-server そして、我々がちょうど作成した100の記事のリストをホームページで見てください!
我々は、第2のステップとして非常に単純なページネーションを加える準備ができています.

無限スクロールのないページネーションの追加
Pageinationのために、我々はBasecamp Teamと呼ばれる非常に単純な宝石を使用しますgeared pagination . それは非常に小さい(私はこの記事を書くときに50未満のコミット)と非常によく書かれています.
宝石を宝石ファイルに加えてインストールしましょう.その後、サーバーを再起動することを忘れないでください!
bundle add geared_pagination
bundle install
宝石を使用すると、非常に簡単です、私たちはset_page_and_extract_portion_from 以下のようなコントローラでのメソッド
# app/controllers/articles_controller.rb

class ArticlesController < ApplicationController
  def index
    # Note that we specify that we want 10 articles per page here with the
    # `per_page` option
    @articles = set_page_and_extract_portion_from Article.all, per_page: [10]
  end
end
ビューでは、ページの末尾にPaginationロジックを追加する必要があります.
<!-- app/views/articles/index.html.erb -->

<h1>Articles#index</h1>

<% @articles.each do |article| %>
  <article>
    <h2><%= article.title %></h2>
    <p><%= article.content %></p>
  </article>
<% end %>

<% unless @page.last? %>
  <%= link_to "Next page", root_path(page: @page.next_param) %>
<% end %>
Pagination作品!次のページリンクをクリックして、ページが変更されます.しかし、それは我々が欲しいものでありません!我々が欲しいものは無限のスクロールです、そして、それはこの記事の最も面白い部分です!

刺激による無限スクロールページ付けの追加
無限スクロールは次のように動作します.
  • ビューポートが隠しページの次のリンクを横切るたびに、追加の記事を取得するAjaxリクエストをトリガーします
  • その後、リストにそれらの記事を追加し、次のページと現在の次のページのリンクを置き換えます
  • 我々は、最後のページに到達するまで、プロセスを繰り返します!
  • 準備ができましたか.レッツゴー!
    まず、刺激を伴うページ付けコントローラを作成し、記事インデックスページに接続しましょう.
    を加えましょうnextPageLink コントローラが初期化されたときにターゲットをコンソールに記録します.
    // app/javascript/controllers/pagination_controller.js
    
    import { Controller } from "stimulus"
    
    export default class extends Controller {
      static targets = ["nextPageLink"]
    
      initialize() {
        console.log(this.nextPageLinkTarget)
      }
    }
    
    それを動作させるには、追加することでHTMLを更新する必要がありますdata-controller="pagination" 記事一覧とdata-pagination-target="nextPageLink" をクリックします.インデックスコードは次のようになります.
    <!-- app/views/articles/index.html.erb -->
    
    <div data-controller="pagination">
      <% @articles.each do |article| %>
        <article>
          <h2><%= article.title %></h2>
          <p><%= article.content %></p>
        </article>
      <% end %>
    
      <% unless @page.last? %>
        <%= link_to "Next page",
                    root_path(page: @page.next_param),
                    data: { pagination_target: "nextPageLink" } %>
      <% end %>
    </div>
    
    ページを更新し、次のページリンクをコンソールにログインしてください.
    すべてが正しく配線されているので、我々は我々の機能を追加する準備が整いました.まず最初にやることはconsole.log("intersection") ビューポートが次のページリンクと交差するとき.
    どうやってやるの?
    JavaScriptオブジェクトをIntersecionObserver ! The Intersection Observer API ターゲット要素の祖先要素またはトップレベルドキュメントのビューポートとの交差点の変更を非同期的に観察する方法を提供します.
    我々の刺激コントローラにこれを加えましょう:
    // app/javascript/controllers/pagination_controller.js
    
    import { Controller } from "stimulus"
    
    export default class extends Controller {
      static targets = ["nextPageLink"]
    
      initialize() {
        this.observeNextPageLink()
      }
    
      // private
    
      async observeNextPageLink() {
        if (!this.hasNextPageLinkTarget) return
    
        await nextIntersection(this.nextPageLinkTarget)
        console.log("intersection")
      }
    }
    
    const nextIntersection = (targetElement) => {
      return new Promise(resolve => {
        new IntersectionObserver(([element]) => {
          if (!element.isIntersecting) {
            return
          } else {
            resolve()
          }
        }).observe(targetElement)
      })
    }
    
    すごい!それは機能の最も複雑な部分です!それを壊しましょう.
    まず、刺激コントローラが初期化されると、次のページリンクを観察し始める.
    initialize() {
      this.observeNextPageLink()
    }
    
    ページの次のページ・リンクがないならば、コントローラは何もしません.しかし、次のページリンクがあるならば、我々は交差点を待ちますconsole.log("intersection") . このプロセスは非同期であることに注意してください:私たちは、次の交差点が発生する予定がわからない!
    どのように、我々は非同期JavaScriptをしますか?非同期/待機して約束!
    機能observeNextPageLink この理由は非同期です.どのように現在の英語のように読むかを参照してください?次のページリンクと次の交差点を待つconsole.log("intersection") .
    async observeNextPageLink() {
      if (!this.hasNextPageLinkTarget) return
    
      await nextIntersection(this.nextPageLinkTarget)
      console.log("intersection")
    }
    
    最後にはnextIntersection 関数はPromise これは次のページリンクがビューポートと交差するときに解決されます.これは簡単に新しいIntersectionObserver それは次のページリンクを観察するでしょう.
    const nextIntersection = (targetElement) => {
      return new Promise(resolve => {
        new IntersectionObserver(([element]) => {
          if (!element.isIntersecting) {
            return
          } else {
            resolve()
          }
        }).observe(targetElement)
      })
    }
    
    私たちの整備士が適所にあるので、我々は我々の代わりにする必要があるconsole.log("intersection") 何か役に立つ.代わりに、“交差点”のログをコンソールで、我々は次のページから記事を取得し、我々はすでに持っている記事のリストに追加します!
    RailsでAjaxリクエストを行うには、新しいブランドを使用しますrails/request.js 2021年6月に作成された図書館.この図書館はラッパですfetch 通常はJavaScriptでAJAXリクエストを行うために使用します.これは、自動的にRailsと統合、例えば、それを自動的に設定しますX-CSRF-Token Railsアプリケーションで必要とされるヘッダー、これは我々がそれを使用する理由です!
    パッケージに追加しましょう.糸を使ったJSON :
    yarn add @rails/request.js
    
    さあインポートしましょうget PageGationコントローラの機能と置換console.log("intersection") 実際の論理で.コードは次のようになります.
    import { Controller } from "stimulus"
    import { get } from "@rails/request.js"
    
    export default class extends Controller {
      static targets = ["nextPageLink"]
    
      initialize() {
        this.observeNextPageLink()
      }
    
      async observeNextPageLink() {
        if (!this.hasNextPageLinkTarget) return
    
        await nextIntersection(this.nextPageLinkTarget)
        this.getNextPage()
      }
    
      async getNextPage() {
        const response = await get(this.nextPageLinkTarget.href) // AJAX request
        const html = await response.text
        const doc = new DOMParser().parseFromString(html, "text/html")
        const nextPageHTML = doc.querySelector(`[data-controller~=${this.identifier}]`).innerHTML
        this.nextPageLinkTarget.outerHTML = nextPageHTML
      }
    }
    
    const nextIntersection = (targetElement) => {
      return new Promise(resolve => {
        new IntersectionObserver(([element]) => {
          if (!element.isIntersecting) {
            return
          } else {
            resolve()
          }
        }).observe(targetElement)
      })
    }
    
    ここで唯一の変更はimport { get } from "@rails/request.js" これは、我々のサーバーとのために取得AJAX要求を使用するconsole.log("intersection") それはthis.getNextPage() . この最後の方法を理解しましょう.
    async getNextPage() {
      const response = await get(this.nextPageLinkTarget.href) // AJAX request
      const htmlString = await response.text
      const doc = new DOMParser().parseFromString(htmlString, "text/html")
      const nextPageHTML = doc.querySelector(`[data-controller=${this.identifier}]`).outerHTML
      this.nextPageLinkTarget.outerHTML = nextPageHTML
    }
    
    まず、サーバにGETリクエストを発行し、応答を待ち、response 変数.次に、レスポンスからテキストを抽出し、htmlString 変数.このクエリを使用するにはhtmlString , 最初にHTML文書を作成するために構文解析を行いますDOMParser . 次に、このドキュメントをdoc 変数.次に、このドキュメントから次のページ記事と次のページリンクを展開し、現在の次のページリンクを置き換えることにより、記事リストに追加します.
    私たちの無限のスクロールが動作しますが、1つだけの反復のためです!我々はそれを再帰的にする必要がある.たびに新しい記事は、ページに追加され、新しい次のページのリンクも追加されます!我々は、この無限の次のページのリンクを読む無限のスクロールを持つことができるように観察する必要があります!
    この再帰を加えましょう!
    最終的なコントローラです
    import { Controller } from "stimulus"
    import { get } from "@rails/request.js"
    
    export default class extends Controller {
      static targets = ["nextPageLink"]
    
      initialize() {
        this.observeNextPageLink()
      }
    
      async observeNextPageLink() {
        if (!this.hasNextPageLinkTarget) return
    
        await nextIntersection(this.nextPageLinkTarget)
        this.getNextPage()
    
        await delay(500) // Wait for 500 ms
        this.observeNextPageLink() // repeat the whole process!
      }
    
      async getNextPage() {
        const response = await get(this.nextPageLinkTarget.href)
        const html = await response.text
        const doc = new DOMParser().parseFromString(html, "text/html")
        const nextPageHTML = doc.querySelector(`[data-controller~=${this.identifier}]`).innerHTML
        this.nextPageLinkTarget.outerHTML = nextPageHTML
      }
    }
    
    const delay = (ms) => {
      return new Promise(resolve => setTimeout(resolve, ms))
    }
    
    const nextIntersection = (targetElement) => {
      // Same as before
    }
    
    ここでは、私たちはobserveNextPageLink 関数は、あまりにも高速スクロールを避けるために500 msを待つことによって機能し、我々は、次のページのリンクを観察し、1つは、このように我々はちょうど行った全体のプロセスを繰り返す!
    最後に、あなたはそれが本当の無限のスクロールを作るためにページ上の次のページのリンクを非表示にすることができますと思う.
    <% unless @page.last? %>
      <%= link_to "Next page",
                  root_path(page: @page.next_param),
                  data: { pagination_target: "nextPageLink" },
                  style: "visibility: hidden;" %>
    <% end %>
    
    あなたはそれをやった、レールと刺激で本当の無限のスクロールを構築!

    宅配と有用資源

  • rails/request.js フェッチのラッパを提供するライブラリです.Railsアプリケーションで作業するときに、非常に便利です.なぜなら、Railsアプリケーションで必要とされるフードの下にいくつかのヘッダを設定するからです.

  • basecamp/gearder_pagination は非常に小さなページの宝石(50コミット未満)です.あなたがRuby/Railsでいくつかのトリックを学びたいならば、あなたはコードを読むべきです!
  • JavaScriptの非同期アクションで作業する場合は、約束とasync/waitを使用して動作します.The Intersection Observer API ページ上の他の要素と交差するビューポートに基づいて動作をトリガーするのに役立ちます.

  • クレジット
    この記事は大いに感激しているhey.com 's無限スクロール.Basecampチームのおかげでleaving the source maps open . 私が同様の機能を構築しなければならなかったとき、それは本当に役に立ちました!

    この記事は好きでしたか.
    あなたが新しい記事を発行するときに通知を得ることができます.私は時々この無限のスクロールのような興味深い機能に取り組んでください!