20
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ElixirAdvent Calendar 2022

Day 12

LiveView で無限スクロールデータテーブルをコンポーネント化する

Last updated at Posted at 2022-12-11

この記事は Elixir Advent Calendar 2022 の 12 日目です。

@koyo-miyamura です!
最近は本業の傍ら、Elixirコミュニティの方で副業しており、実務でのElixir実装にハマっております。
「お仕事でも使える Elixir」をテーマに実務での実装例の紹介に励んでおりますb

以前こちらの記事で LiveView での無限スクロールデータテーブルを実装しました。
今回はこちらのコンポーネント化をご紹介します!

edit_20221113214417.gif

バージョン

Phoenix 1.6.15
LiveView 0.17.12

コンポーネントについて

LiveView にはコンポーネントの手法が大きく2つあります。

1つ目は Phoenix.Component というステートレスなコンポーネントを作る機能です。
シンプルで強力なのでまずこちらを使うことが多いでしょう。

2つ目は Phoenix.LiveComponent というステートフルなコンポーネントを作る機能です。
こちらは LiveView ハンドラもセットでついてくるので、状態管理したりライフサイクル管理したり細かいイベントハンドリングできたりします。
ただあまりそういうのが必要な状況も少ないので、基本的には1つ目の Phoenix.Component を試してみて、ダメだったら検討するでいいかなと思います。

スロットを使ったコンポーネント

ではまずスロット機能を使ったコンポーネントを紹介します。
スロットとはコンポーネントに外から html を与える機能です。
React や Vue などにもありますね。

実装

ハンドラの方に alias を貼っておきます。

lib/scrollable_table_sample_web/live/user_live/index.ex
defmodule ScrollableTableSampleWeb.UserLive.Index do
...

  alias ScrollableTableSampleWeb.Components.ScrollableDataTable
...
end

コンポーネントの方はこういう感じで実装します。
Named Slot の機能を使って簡潔に書いています。

lib/scrollable_table_sample_web/live/components/scrollable_table.ex
defmodule ScrollableTableSampleWeb.Components.ScrollableDataTable do
  @moduledoc """
  Scrollable data table.
  """
  use Phoenix.Component

  def table(assigns) do
    ~H"""
      <div style="overflow-y: scroll; max-height: 70vh;" class="infinite-scrollable-table">
        <table>
          <thead>
            <tr>
              <%= for column <- @columns do %>
                <%= column.label %>
              <% end %>
            </tr>
          </thead>
          <tbody
              id="rows"
              phx-update="append"
              phx-hook="InfiniteScroll"
              data-page={@page}
              data-total_pages={@total_pages}
              data-el=".infinite-scrollable-table"
            >
            <%= for row <- @rows do %>
              <tr id={"row-#{row.id}"}>
                <%= for column <- @columns do %>
                  <%= render_slot(column, row) %>
                <% end %>
              </tr>
            <% end %>
          </tbody>
        </table>
      </div>
    """
  end
end

使う側はこんな感じですね。

lib/scrollable_table_sample_web/live/user_live/index.html.heex
<ScrollableDataTable.table rows={@users} page={@page} total_pages={@total_pages}>
  <:columns let={user} label="Name">
    <td><%= user.name %></td>
  </:columns>

  <:columns let={user} label="">
    <td>
      <span><%= live_redirect "Show", to: Routes.user_show_path(@socket, :show, user) %></span>
      <span><%= live_patch "Edit", to: Routes.user_index_path(@socket, :edit, user) %></span>
      <span><%= link "Delete", to: "#", phx_click: "delete", phx_value_id: user.id, data: [confirm: "Are you sure?"] %></span>
    </td>
  </:columns>
</ScrollableDataTable.table>

以下にリポジトリも置いているので気になる方はみてみてください。

コンポーネントを関数単位で細かく分ける方法

スロットを使う方法でもいいのですが、柔軟性に欠ける場合があります。
例えばスロットはコンポーネントの直下に書かないといけない制約があります。

例えば下記のようにはできません。

lib/scrollable_table_sample_web/live/components/scrollable_table.ex
<ScrollableDataTable.table>
  <div>
    <:columns let={user} label="Name">
      <td><%= user.name %></td>
    </:columns>
  </div>
...
</ScrollableDataTable.table>

2022-11-19_20h20_28.png

これだと困ることがあって、例えば DataTable コンポーネントがあって、 ScrollableDataTable はそれを利用して作りたい!みたいな場合に行き詰りますw

なのでテーブルの構造をスロットで表現するのではなく、コンポーネント内の関数単位で表現する手法を紹介します。
こっちの方が記述は煩雑ですが、後から柔軟に構造を組み替えたりできるので、いいかなと思います。

実装

まずデータテーブルを以下のように作ります。
コンポーネントの中にテーブルの構造を関数単位で表現するようにしています。

lib/scrollable_table_sample_web/live/components/data_table.ex
defmodule ScrollableTableSampleWeb.Components.DataTable do
  @moduledoc """
  Data table.
  """
  use Phoenix.Component

  def table(assigns) do
    ~H"""
      <table>
        <%= render_slot(@inner_block) %>
      </table>
    """
  end

  def thead(assigns) do
    ~H"""
      <thead>
        <tr>
          <%= render_slot(@inner_block) %>
        </tr>
      </thead>
    """
  end

  def tbody(assigns) do
    ~H"""
    <%= for row <- @rows do %>
      <tr id={"row-#{row.id}"}>
        <%= render_slot(@inner_block, row) %>
      </tr>
    <% end %>
    """
  end
end

上記のデータテーブルコンポーネントを使って、無限スクロールデータテーブルを作ります。
細かいスタイルはデータテーブルコンポーネントに委譲しつつ、無限スクロールデータテーブルに必要な hook やスタイルを定義するようにしています。

lib/scrollable_table_sample_web/live/components/scrollable_table.ex
defmodule ScrollableTableSampleWeb.Components.ScrollableDataTable do
  @moduledoc """
  Scrollable data table.
  """
  use Phoenix.Component
  alias ScrollableTableSampleWeb.Components.DataTable

  def table(assigns) do
    ~H"""
      <div style="overflow-y: scroll; max-height: 70vh;" class="infinite-scrollable-table">
        <DataTable.table>
          <%= render_slot(@inner_block) %>
        </DataTable.table>
      </div>
    """
  end

  def thead(assigns) do
    ~H"""
      <DataTable.thead>
        <%= render_block(@inner_block) %>
      </DataTable.thead>
    """
  end

  def tbody(assigns) do
    ~H"""
      <tbody
        id="rows"
        phx-update="append"
        phx-hook="InfiniteScroll"
        data-page={@page}
        data-total_pages={@total_pages}
        data-el=".infinite-scrollable-table"
      >
        <DataTable.tbody let={row} rows={@rows}>
          <%= render_block(@inner_block, row) %>
        </DataTable.tbody>
      </tbody>
    """
  end
end

使う側はこんな感じです。ちょっと記述増えますが柔軟に記述できます。

lib/scrollable_table_sample_web/live/user_live/index.html.heex
<ScrollableDataTable.table>
  <ScrollableDataTable.thead>
    <th>Name</th>
    <th></th>
  </ScrollableDataTable.thead>

  <ScrollableDataTable.tbody rows={@users} let={user} page={@page} total_pages={@total_pages}>
    <td><%= user.name %></td>
    <td>
      <span><%= live_redirect "Show", to: Routes.user_show_path(@socket, :show, user) %></span>
      <span><%= live_patch "Edit", to: Routes.user_index_path(@socket, :edit, user) %></span>
      <span><%= link "Delete", to: "#", phx_click: "delete", phx_value_id: user.id, data: [confirm: "Are you sure?"] %></span>
    </td>
  </ScrollableDataTable.tbody>
</ScrollableDataTable.table>

こちらも以下にリポジトリも置いているので気になる方はみてみてください!

まとめ

LiveView で無限スクロールデータテーブルをコンポーネント化する手法を紹介しました。
2パターン紹介しましたが筆者的には後者の方がおすすめです。
(前者でもっといいやり方あるかもしれませんがw)

コンポーネントに抽象化することで再利用も簡単なのでぜひやっていきましょう。

ちなみに LiveView 0.18 だとまた Phoenix.Component の文法が変わります。
具体的には Slot の機能がさらに強化されますね。
0.18 ver の実装出来たらぜひ記事にしてみてくださいb

次回は
@GeekMasahiro さんの「Elixirらしいif(xx){str += xxx}はこれだ」
です!お楽しみに!

20
4
0

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
20
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?