はじめに
この記事は Elixirアドベントカレンダーのシリーズ4の8日目の記事です
PhoenixにビルドインのコンポーネントのCoreComponentの紹介と
ネイティブでは使用できないブラウザのConfirmダイアログを独自に実装します
CoreComponentとは
Phoenixにビルドインされた TailwindとLiveView Phoniex Componentで実装されたデフォルトコンポーネント群で以下のようなコンポーネントがあります
- modal -> CRUD作成時にも使用されるモーダル
- flash -> 保存、エラー時に右上に表示されるフラッシュ
- flash_group -> 上記をまとめたもの
- simple_form -> input部分と保存、キャンセル等のボタンのレイアウトを調整したフォーム
- button -> 各丸ボタン
- input -> 各種各丸input、正規化やエラーメッセージ表示も対応
- label -> デザイン調整されたラベル
- error -> input等で使うエラーメッセージ
- header -> タイトル、サブタイトル、アクション含むデザインヘッダー
- table -> LiveStream対応したデザイン調整されたテーブル
- list -> 構造体やMapの情報を一覧する
- back -> 戻るボタン
- icon -> Heroiconを表示するコンポーネント
attr と slot
CoreComponentの元になっているPhoenix.Componentは attrとslotという値を定義することができます
attrはパラメーターとして渡せる値を定義できます
slotはcomponentのタグで挟んだ内容をどの場所で展開するかを定義できます
実際の実装を見てみましょう
@doc """
Renders a back navigation link.
## Examples
<.back navigate={~p"/posts"}>Back to posts</.back>
"""
attr :navigate, :any, required: true
slot :inner_block, required: true
def back(assigns) do
~H"""
<div class="mt-16">
<.link
navigate={@navigate}
class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
>
<.icon name="hero-arrow-left-solid" class="h-3 w-3" />
<%= render_slot(@inner_block) %>
</.link>
</div>
"""
end
Examplesに使い方が載っています
attr
にnavigate
、slot
に Back to Posts
が指定されています
attrのnavigateはlinkタグのnavigateにパスとして展開されています
slotの中身を展開するときは render_slot
を使用します
core_component カスタム
CoreComponentですが細かいところがスマホアプリのWebViewと合わないので微調整を行います
変更する場合はcore_components.ex
に全てあるので、対象のコンポーネントを検索してclassを値を変更することで調整ができます
.table
コンポーネントが横に広くてスクロールが必要になるのが嫌なので、固定値からw-full
にしましょう
def table(assigns) do
assigns =
with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do
assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end)
end
~H"""
<div class="overflow-y-auto px-4 sm:overflow-visible sm:px-0">
- <table class="mt-11 w-[40rem] sm:w-full">
+ <table class="mt-11 w-full">
# 以下省略
"""
end
confirm modalを追加
WebViewだと削除時に出てくる confirmやalertのダイアログは出ないので独自に実装が必要です
削除リンクをモーダルを開くナビゲーションにして
index.html.heexの末尾にconfirm modalを表示するようにします
クリックイベントからモーダル表示用のリンクに差し替え、 live_actionがdeleteの時に開くモーダルを追加します
...
<.table
id="shelves"
rows={@streams.shelves}
row_click={fn {_id, shelf} -> JS.navigate(~p"/shelves/#{shelf}") end}
>
<:col :let={{_id, shelf}} label="Name"><%= shelf.name %></:col>
<:action :let={{_id, shelf}}>
<div class="sr-only">
<.link navigate={~p"/shelves/#{shelf}"}>Show</.link>
</div>
<.link patch={~p"/shelves/#{shelf}/edit"}>Edit</.link>
- </:action>
- <:action :let={{id, shelf}}>
- <.link
- phx-click={JS.push("delete", value: %{id: shelf.id}) |> hide("##{id}")}
- data-confirm="Are you sure?"
- >
- Delete
- </.link>
+ <.link patch={~p"/shelves/#{shelf}/delete"}>Delete</.link>
</:action>
</.table>
<.modal :if={@live_action in [:new, :edit]} id="shelf-modal" show on_cancel={JS.patch(~p"/shelves")}>
<.live_component
module={BookshelfWeb.ShelfLive.FormComponent}
id={@shelf.id || :new}
title={@page_title}
action={@live_action}
shelf={@shelf}
patch={~p"/shelves"}
/>
</.modal>
+ <.modal
+ :if={@live_action in [:delete]}
+ id="shelf-delete-modal"
+ show
+ on_cancel={JS.navigate(~p"/shelves")}
+ >
+ <p class="mb-4 text-lg">Are you sure?</p>
+ <.button phx-click={JS.push("delete", value: %{id: @shelf.id})}>Delete</.button>
+ </.modal>
次にlive_actionのハンドリングを行います
actionがdeleteの場合はパラメーターのidからshelfを取得してアサインします
defmodule BookshelfWeb.ShelfLive.Index do
use BookshelfWeb, :live_view
...
defp apply_action(socket, :index, _params) do
socket
|> assign(:page_title, "Listing Shelves")
|> assign(:shelf, nil)
end
+ defp apply_action(socket, :delete, %{"id" => id}) do
+ socket
+ |> assign(:page_title, "Delete Shelf")
+ |> assign(:shelf, Shelves.get_shelf!(id))
+ end
...
end
deleteボタンを押した時のイベントを修正します
@impl true
def handle_event("delete", %{"id" => id}, socket) do
shelf = Shelves.get_shelf!(id)
{:ok, _} = Shelves.delete_shelf(shelf)
- {:noreply, stream_delete(socket, :shelves, shelf)}
+ {
+ :noreply,
+ socket
+ # flashを表示
+ |> put_flash(:info, "Shelf deleted successfully")
+ # モーダルを閉じるように変更
+ |> push_navigate(to: ~p"/shelves")
+ |> stream_delete(:shelves, shelf)
+ }
end
routeにdelete actionを追加します
scope "/", BookshelfWeb do
pipe_through :browser
get "/", PageController, :home
live "/shelves", ShelfLive.Index, :index
live "/shelves/new", ShelfLive.Index, :new
live "/shelves/:id/edit", ShelfLive.Index, :edit
+ live "/shelves/:id/delete", ShelfLive.Index, :delete
live "/shelves/:id", ShelfLive.Show, :show
live "/shelves/:id/show/edit", ShelfLive.Show, :edit
end
これでconfirmダイアログ風なモーダルを通して削除をができるようになりました
最後に
本記事ではCoreComponentの使い方、カスタムについての解説と
実際に使って削除確認ダイアログを実装しました
次はUIライブラリの追加とナビゲーションコンポーネントを作成してきます
本記事は以上になります、ありがとうございました