Phoenix LiveViewの基本設定に関する備忘録です。phx.gen.htmlで生成したユーザ登録のHTMLをSPA化(LiveView化)したイメージです。ユーザ登録フォームとユーザ一覧からなるページです。ユーザ登録すると、ページ遷移することなしに、自動的にユーザ一覧が更新されます。
Phoenix1.6の基本的な仕組み - Qiita
【参考チュートリアル】
LiveView Chat Tutorial
【過去記事】
-
LiveView関連
東京電力電力供給状況監視 - Phoenix LiveView
Phoenix LiveView と キーボードイベント - Qiita
Phoenix LiveView の JavaScript Hook - Qiita
Phoenix LiveView とTailwind と daisyUI - Qiita
Phoenix LiveViewの基本設定 - Qiita
Phoenix1.6の基本的な仕組み - Qiita -
認証関連
Phoenix 認証システム - mix phx.gen.auth
Elixir/Phoenix のシンプル認証 auth_plug -
Ecto関連
Elixir Ecto チュートリアル - Qiita
Elixir Ecto のまとめ - Qiita
Elixir Ecto Association - Oiita
実行環境
Elixir 1.13.0
Phoenix 1.6.12
postgreSQL設定済み
1.LiveViewの基本設定
1-1.プロジェクト作成
まずはプロジェクトを作成します。
mix phx.new liveview_people --no-mailer --no-dashboard
cd liveview_people
mix ecto.setup
PostgreSQL のポートが正しくないと、mix ecto.setup でエラーになります。私の Windows の環境ではデフォルトの 5432 ではなく 5433 だったので以下のように変更する必要がありました。
config :liveview_people, LiveviewPeople.Repo,
username: "postgres",
password: "postgres",
hostname: "localhost",
database: "liveview_people_dev",
port: 5433,
stacktrace: true,
show_sensitive_data_on_connection_error: true,
pool_size: 10
1-2.LiveView controller
LiveViewの慣例に従ってlib/liveview_people_web の下に liveディレクトリを作成します。次に LiveView controller の people_live.ex を作成します。基本的なLiveViewの設定はここで行います。
defmodule LiveviewPeopleWeb.PeopleLive do
use LiveviewPeopleWeb, :live_view
def mount(_params, _session, socket) do
{:ok, socket}
end
def render(assigns) do
LiveviewPeopleWeb.UserView.render("users.html", assigns)
end
end
通常 LiveView controller には以下の3つを定義します。
- (1) mount関数
- (2) render関数
- (3) event handler
mount関数でviewの初期状態を定義します。今回の場合、後で見るように changeset と users の初期状態を設定することになります。
render関数では描画の仕方を指定します。ここではusers.html.heexを指定します。拡張子がheexであることに注意してください。
render関数を明示的に設定せずに、lib/liveview_people_web/live/people_live.html.heex というテンプレートファイルを作成し、暗示的に rennder関数を指定することも可能な場合もあります。--> 「Phoenix LiveView の JavaScript Hook」 しかし私のWindows環境では明示的に指定しないとエラーになりましたが、現在原因の特定はできていません。
event handlerはHTMLに組み込まれたボタンを押したときに発生するイベントなどのcallbackを定義します。今回の場合、changesetとusersを更新することで、HTMLの表示に変更を与えます。
viewの基本的な設定を行います。
defmodule LiveviewPeopleWeb.UserView do
use LiveviewPeopleWeb, :view
end
ここでrender関数で指定した以下の外部ファイルを用意します。拡張子はheexです。
<h1>LiveView People Page</h1>
またroot.html.heexの body部分を以下のものに置き換えます。
<body>
<header>
<section class="container">
<h1>LiveView People Example</h1>
</section>
</header>
<%= @inner_content %>
</body>
1-3.routerの修正
router.exの"/"パスを以下のようにLiveView controllerを指すように修正します。
scope "/", LiveviewPeopleWeb do
pipe_through :browser
live "/", PeopleLive
end
1-4.基本設定の確認
以上でLivwViewの基本設定が終わったので、サーバを起動し確認します。
mix phx.server
http://localhost:4000/ にアクセスして確認します。
無事ページの表示ができたので成功です。次のステップでLiveViewの肉付けを行っていきます。
2.次のステップ
2-1.Schema作成など
次のコマンドでSchemaを作成します。ユーザ登録用のテーブルを用意します。
mix phx.gen.schema User users first_name:string last_name:string age:integer
mix ecto.migrate
Schema モジュールを以下のように修正します。特別なcontext moduleは用意しませんので、ここにcreate_userとlist_usersを定義します。
defmodule LiveviewPeople.User do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
alias LiveviewPeople.Repo
alias __MODULE__
schema "users" do
field :age, :integer
field :first_name, :string
field :last_name, :string
timestamps()
end
@doc false
def changeset(user, attrs) do
user
|> cast(attrs, [:first_name, :last_name, :age])
|> validate_required([:first_name, :last_name, :age])
end
def create_user(attrs) do
%User{}
|> changeset(attrs)
|> Repo.insert()
end
def list_users do
User
|> Repo.all()
end
end
LiveView controller を以下のように修正します。
defmodule LiveviewPeopleWeb.PeopleLive do
use LiveviewPeopleWeb, :live_view
alias LiveviewPeople.User
def mount(_params, _session, socket) do
users = User.list_users() |> Enum.reverse()
changeset = User.changeset(%User{}, %{})
{:ok, assign(socket, changeset: changeset, users: users)}
end
def handle_event("new_user", %{"user" => params}, socket) do
case User.create_user(params) do
{:error, changeset} ->
{:noreply, assign(socket, changeset: changeset)}
{:ok, _user} ->
changeset = User.changeset(%User{}, %{})
users = User.list_users() |> Enum.reverse()
{:noreply, assign(socket, changeset: changeset, users: users)}
end
end
def render(assigns) do
LiveviewPeopleWeb.UserView.render("users.html", assigns)
end
end
mount では users と changeset の初期値を設定します。これが template に反映され初期画面となります。
またユーザ登録に対応した "new_user" イベントに対する event handler を定義します。エラー時にはchangeset にエラーの内容が渡されます。成功時は users を取得し直して template に反映させます。ページ遷移なしに反映されるのが LiveView の醍醐味ですね。
changeset = User.changeset(%User{}, %{})
この changeset は「validation: :required」エラーを含んだまま、template の formタグに渡されますが、エラー表示はされません。changeset.action=nil だからです。 ==> A note on :errors
templateを以下のように更新します。
<.form let={f} for={@changeset} id="form" phx-submit="new_user">
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<%= label f, :first_name %>
<%= text_input f, :first_name, id: "first_name", placeholder: "first_name" %>
<%= error_tag f, :first_name %>
<%= label f, :last_name %>
<%= text_input f, :last_name, id: "last_name", placeholder: "last_name" %>
<%= error_tag f, :last_name %>
<%= label f, :age %>
<%= number_input f, :age, id: "age", placeholder: "age" %>
<%= error_tag f, :age %>
<div>
<%= submit "Save" %>
</div>
</.form>
<h1>Listing Users</h1>
<table>
<thead>
<tr>
<th>First name</th>
<th>Last name</th>
<th>Age</th>
</tr>
</thead>
<tbody>
<%= for user <- @users do %>
<tr>
<td><%= user.first_name %></td>
<td><%= user.last_name %></td>
<td><%= user.age %></td>
</tr>
<% end %>
</tbody>
</table>
これは基本的には 「mix phx.gen.html」 コマンドで生成したindex.html.heexとform.html.heexを合成したHTMLです。特にこのform構文は heex でないと使えないので注意が必要です。詳細は以下の過去記事を参照してください。
Phoenix1.6の基本的な仕組み - Qiita
初期画面です。changesもusersも空なので、空白のページとなっています。
3人のユーザを登録した画面です。usersには3人入っている状態です。
4人目を登録しようとして、Last Nameが未入力でエラーになった状態です。ちゃんとエラーがでて、エラーじゃないfieldは値が入ったままであることに注意してください。changeset.changes にはエラーでないfieldの値が保持され、changeset.errors にはエラーfieldに対するメッセージが入っています。エラーかどうかはchangeset.action がnilであるかどうかで判断します。成功時はnilですが、今回のエラーの場合、changeset.action に"insert"が入っているのでRepo.insert()で失敗したことがわかります。
今回は以上です。