Phoenix v1.7.10／LiveView v0.20.1でScrivenerを使おうとした② -ページネーションコンポーネントの作成

こんにちは！
プログラミング未経験文系出身、Elixirの国に迷い込んだ？！見習いアルケミストのaliceと申します。
今回はPhoenix1.7にScrivenerを導入してみた手順をまとめます。
なお、今回の記事は下記の記事を参考にPhoenix1.7でやってみたログです。

 https://qiita.com/koyo-miyamura/items/663230bf4bc61e80a711

# 目次
1.[Scrivener導入](https://qiita.com/Alicesky2127/items/7e53e8c86c07b909ea58)
2.ページネーションコンポーネントの作成(本記事)

# 目的 
Phoenix1.7にScrivenerを導入してページネーションをつけたい

## 実行環境
Windows 11 ＋ WSL2 ＋ Ubuntu 22.04
Elixir v1.14.3
Erlang v26.0.2
Phoenix v1.7.10
LiveView v0.20.1

# 今回やりたいこと
Phoenix1.7.10／LiveView v0.20.1のプロジェクトにScrivenerを導入したい。

# 画面上に表示するページネーションコンポーネントを作る
[元記事様](https://qiita.com/koyo-miyamura/items/663230bf4bc61e80a711#%E3%83%9A%E3%83%BC%E3%82%B8%E3%83%8D%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90)の手順に従って作っていきます。

## 関数コンポーネントを作る
Phoenix1.7に合わせて置き場所を`core_components.ex`に置きました。
```diff_elixir:lib/nov11_web/components/core_components.ex
+ def paginator(assigns) do
+     ~H"""
+     <div>
+       <div>
+         <p>表示件数：</p>
+         <div>
+           <form phx-change="update_page_size">
+             <select name="page_size">
+               <option value="5" selected={@page_size == 5}>5</option>
+               <option value="10" selected={@page_size == 10}>10</option>
+               <option value="15" selected={@page_size == 15}>15</option>
+               <option value="20" selected={@page_size == 20}>20</option>
+               <option value="25" selected={@page_size == 25}>25</option>
+             </select>
+           </form>
+         </div>
+         <p><%= @total_entries %>件中 <%= @page_size * (@page - 1) + 1 %>~<%= @page_size * @page %>件を表示</p>
+       </div>
+       <div>
+         <button phx-click="update_page" phx-value-page="1" disabled={@page == 1}>先頭へ</button>
+         <button phx-click="update_page" phx-value-page={@page - 1} disabled={@page == 1}>前へ</button>
+         <button phx-click="update_page" phx-value-page={@page + 1} disabled={@page == @total_pages}>次へ</button>
+         <button phx-click="update_page" phx-value-page={@total_pages} disabled={@page == @total_pages}>最後尾へ</button>
+       </div>
+     </div>
+     """
+   end
```

## ビューにコンポーネントを設置
Phoenix1.7に合わせて`core_components.ex`内に作った関数コンポーネントを呼び出しました。
```diff_elixir:lib/nov11_web/live/user_live/index.html.heex
+ <.paginator
+  total_entries={@users.total_entries}
+  page_size={@users.page_size} 
+  page={@users.page_number} 
+  total_pages={@users.total_pages} 
+ >
+ </.paginator>
```

## Contextを修正
元記事様そのままです。
```diff_elixir:lib/nov11/users.ex
 def list_users() do
    build_list_query()
    |> Repo.paginate()
  end

  def list_users(page, page_size) do
    build_list_query()
    |> Repo.paginate(page: page, page_size: page_size)
  end

  defp build_list_query() do
    from(u in User,
      order_by: [desc: u.id]
    )
  end
```

## LiveView側の記述を修正
ページが変更されたときに、ページサイズがリセットされないよう、すでにセット済みの値を考慮してリダイレクトしたいのですが、下記関数がLiveView v0.20.1では廃止されて使用できなかったので下記の通り変更しました。
　・push_redirect -> push_navigateでクエリパラメータを渡すように変更

```diff_elixir:lib/nov11_web/live/user_live/index.ex
defmodule Nov11Web.UserLive.Index do
  use Nov11Web, :live_view

  alias Nov11.Users
  alias Nov11.Users.User

  @default_page 1
  @default_page_size 5

  @impl true
  def mount(_params, _session, socket) do
    {:ok, stream(socket, :users, Users.list_users())}
  end

  @impl true
  def handle_params(params, _url, socket) do
    {:noreply, apply_action(socket, socket.assigns.live_action, params)}
  end

  defp apply_action(socket, :edit, %{"id" => id}) do
    socket
    |> assign(:page_title, "Edit User")
    |> assign(:user, Users.get_user!(id))
  end

  defp apply_action(socket, :new, _params) do
    socket
    |> assign(:page_title, "New User")
    |> assign(:user, %User{})
  end

  defp apply_action(socket, :index, params) do
    socket
    |> assign(:page_title, "Listing Users")
    |> assign(:user, nil)
    |> assign(:users, list_users(params))
  end

  @impl true
  def handle_info({Nov11Web.UserLive.FormComponent, {:saved, user}}, socket) do
    {:noreply, stream_insert(socket, :users, user)}
  end

  @impl true
  def handle_event("delete", %{"id" => id}, socket) do
    user = Users.get_user!(id)
    {:ok, _} = Users.delete_user(user)

    {:noreply, stream_delete(socket, :users, user)}
  end

  @impl true
  def handle_event("update_page", %{"page" => page}, socket) do
-     params =
-       socket.assigns
-       |> Map.get(:users)
-       |> Map.take([:page_number, :page_size])
-       |> Map.merge(%{page_number: page})
-       |> Keyword.new()

+     page_size =
+       socket.assigns
+       |> Map.get(:users)
+       |> Map.take([:page_number, :page_size])
+       |> Map.merge(%{page_number: page})
+       |> Keyword.new()
+       |> Keyword.get(:page_size)

+     page_number =
+       socket.assigns
+       |> Map.get(:users)
+       |> Map.take([:page_number, :page_size])
+       |> Map.merge(%{page_number: page})
+       |> Keyword.new()
+       |> Keyword.get(:page_number)

-     {:noreply,
-      push_redirect(socket,
-        to: Routes.user_index_path(socket, :index, params)
-      )}

+    {:noreply,
+     push_navigate(socket, to: ~p"/users?page_number=#{page_number}&page_size=#{page_size}")}
+  end

  @impl true
  def handle_event("update_page_size", %{"page_size" => page_size}, socket) do
-     params =
-       socket.assigns
-       |> Map.get(:users)
-       |> Map.take([:page_number, :page_size])
-       |> Map.merge(%{page_size: page_size})
-       |> Keyword.new()

+    page_size =
+      socket.assigns
+      |> Map.get(:users)
+      |> Map.take([:page_number, :page_size])
+      |> Map.put(:page_size, page_size)
+      |> Map.get(:page_size)

+    page_number =
+      socket.assigns
+      |> Map.get(:users)
+      |> Map.take([:page_number, :page_size])
+      |> Map.get(:page_number)

-    {:noreply,
-      push_redirect(socket,
-        to: Routes.user_index_path(socket, :index, params)
-      )}

+    {:noreply,
+    push_navigate(socket, to: ~p"/users?page_number=#{page_number}&page_size=#{page_size}")}
+  end

  defp list_users(%{"page_number" => page, "page_size" => page_size}) do
    Users.list_users(page, page_size)
  end

  defp list_users(%{"page_number" => page}) do
    Users.list_users(page, @default_page_size)
  end

  defp list_users(%{"page_size" => page_size}) do
    Users.list_users(@default_page, page_size)
  end

  defp list_users(%{}) do
    Users.list_users()
  end
end
```

# 不明点(解決出来たら別記事化します)
・クエリパラメータは渡せたが画面上での更新がされません
　→クエリパラメータを受け取る方法が分かっていません
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677280/d5e76f5b-3a8a-4df9-b5db-044eab2d24d5.png)


下記検証の結果、`list_users(page, page_size)`で%Scrivener.Page{}内のpage_numberとpage_sizeの値が更新されることが分かっています。
では、クエリパラメータをどうやって受け取って上記の変数`page`, `page_size`にそれぞれ当てはめる？...ここが分かっていない箇所です。

```shell:bash
iex -S mix
```
```diff_elixir:iex
iex(1)> Nov11.Users.list_users()    
%Scrivener.Page{
  page_number: 1,
  page_size: 5,
  total_entries: 23,
  total_pages: 5,
  entries: [
    %Nov11.Users.User{
    ...
``` 

```diff_elixir:iex
iex(2)> Nov11.Users.list_users(2, 3)
%Scrivener.Page{
  page_number: 2,
  page_size: 3,
  total_entries: 23,
  total_pages: 8,
  entries: [
    %Nov11.Users.User{
    ...
```

# 現時点で学んだこと
1.Scrivenerを導入すること自体はできました(画面側がダメなので追って理解を深めたいと思います)
 
2.Phoenix1.7.10／LiveView v0.20.1の環境では`push_redirect`が廃止されており、リダイレクトの書き方が変わっているということを学びました。

https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#push_redirect/2

# ～Elixirの国のご案内～
↓Elixirって何ぞや？と思ったらこちらもどぞ。Elixirは先端のアレコレをだいたい全部できちゃいます:laughing::sparkles::sparkles:

https://qiita.com/piacerex/items/09876caa1e17169ec5e1

↓ゼロからElixirを始めるなら「エリクサーチ」がおすすめ！私もエンジニア未経験から学習中です。

https://elixir-lang.info/topics/entry_column


↓[We Are The Alchemists, my friends!:bouquet:](https://qiita.com/torifukukaiou/items/6bcd5bbb80df9e594e9e "Awesome-san's Awesome article")[^10]
Elixirコミュニティは本当に優しくて温かい人たちばかり！
私が挫折せずにいられるのもこの恵まれた環境のおかげです。
まずは気軽にコミュニティを訪れてみてください。[^11]


https://speakerdeck.com/elijo/elixirkomiyunitei-falsebu-kifang-guo-nei-onrainbian




[^10]: @torifukukaiouさんのAwesomeな名言をお借りしました。Elixirコミュニティを一言で表すと、これに尽きます。


[^11]: @kn339264さんの素敵なスライドをお借りしました。Elixirコミュニティはいろんな形で活動中！