LoginSignup
14
4

More than 3 years have passed since last update.

LiveViewでinfinite scroll (Elixir/Phoenix)

Last updated at Posted at 2020-08-27

はじめに

InfiniteScroll.gif

  • こんな感じです
    • 右側のバーが最初は長いのですが、スクロールをすると短くなっていく様子がわかるとおもいます
    • いつも動いているかは自信がありませんが、Koto Faucet で同様の操作ができます
  • Phoenix v1.5.4
  • Elixir 1.10.4-otp-23
  • Erlang 23.0.1
  • OkazaKirin.beam #26での成果です

phx.newからやってみる

  • ElixirPhoenixの準備はできているものとします
  • 以下、ざざっと変更内容をまとめておきます
$ mix phx.new hello --live
$ mix ecto.create
$ mix phx.server
$ mix phx.gen.live Accounts User users username email phone_number
lib/hello_web/router.ex
  scope "/", HelloWeb do
    pipe_through :browser

    live "/", PageLive, :index

    live "/users", UserLive.Index, :index # 追加
    live "/users/new", UserLive.Index, :new # 追加
    live "/users/:id/edit", UserLive.Index, :edit # 追加

    live "/users/:id", UserLive.Show, :show # 追加
    live "/users/:id/show/edit", UserLive.Show, :edit # 追加
  end
priv/repo/seeds.exs
for i <- 1..1000 do
  {:ok, _} =
    Hello.Accounts.create_user(%{
      username: "user#{i}",
      name: "User #{i}",
      email: "user#{i}@test",
      phone_number: "555-555-5555"
    })
end
$ mix ecto.migrate
$ mix run priv/repo/seeds.exs
lib/hello_web/live/user_live/index.html.leex
  <tbody id="users"
         phx-update="append"
         phx-hook="InfiniteScroll"
         data-page="<%= @page %>">
lib/hello_web/live/user_live/index.ex
defmodule HelloWeb.UserLive.Index do
  use HelloWeb, :live_view

  alias Hello.Accounts
  alias Hello.Accounts.User

  @impl true
  def mount(_params, _session, socket) do
    {:ok,
     socket
     |> assign(page: 1, per_page: 10)
     |> fetch(), temporary_assigns: [users: []]}
  end

  defp fetch(%{assigns: %{page: page, per_page: per}} = socket) do
    assign(socket, users: Accounts.list_users(page, per))
  end

  def handle_event("load-more", _, %{assigns: assigns} = socket) do
    {:noreply, socket |> assign(page: assigns.page + 1) |> fetch()}
  end
lib/hello/accounts.ex
  def list_users(current_page, per_page) do
    Repo.all(
      from u in User,
        order_by: [asc: u.id],
        offset: ^((current_page - 1) * per_page),
        limit: ^per_page
    )
  end
assets/js/app.js
let Hooks = {}

let scrollAt = () => {
  let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
  let scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight
  let clientHeight = document.documentElement.clientHeight

  return scrollTop / (scrollHeight - clientHeight) * 100
}

Hooks.InfiniteScroll = {
  page() { return this.el.dataset.page },
  mounted(){
    this.pending = this.page()
    window.addEventListener("scroll", e => {
      if(this.pending == this.page() && scrollAt() > 90){
        this.pending = this.page() + 1
        this.pushEvent("load-more", {})
      }
    })
  },
  updated(){ this.pending = this.page() }
}

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {hooks: Hooks, params: {_csrf_token: csrfToken}})

Wrapping Up

14
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
4