前回までで、コンテキスト周りが一通り完成しました。
今回は、いよいよLiveViewでチャットの機能を実装していきます。
本記事で、LiveViewを用いたコメントの表示が可能になります。
本記テーマは7記事構成になっています。
7記事それぞれで取り扱う内容は以下です。
本テーマは以下の方針で記述します。
完成版の画面イメージは以下のようになっています。
前回の記事で作成されていたBoardControllerをLiveView用に修正します。
もともとの render/2
を、
# /lib/lv_chat_web/controllers/board_controller.ex
def show(conn, %{"id" => id}, _current_user) do
board = Meeting.get_board!(id)
render(conn, "show.html", board: board)end
live_render/3
で置き換えます。
# /lib/lv_chat_web/controllers/board_controller.ex
alias Phoenix.LiveView
def show(conn, %{"id" => id}, current_user) do
board = Meeting.get_board!(id)
LiveView.Controller.live_render( conn, LvChatWeb.BoardLiveView, session: %{ board: board, current_user: current_user, } )end
/lib/lv_chat_web/views/
に board_live_view.ex
というファイルを作成します。
以下の3つの関数を実装します。
render/1
mount/2
handle_event/3
Phoenix.View.renderを使用します。
def render(assigns) do
Phoenix.View.render(BoardView, "show.html", assigns)
end
初期表示時は、すべてのコメントを読み込むようにします。
def mount(session = %{current_user: _user, board: board}, socket) do
assigns =
Map.merge(
session,
%{
comments: board |> Meeting.list_comments,
comment: LvChat.Meeting.change_comment
}
)
{:ok, assign(socket, assigns)}
end
form側で指定されている phx-submit="add_comment"
に対応するイベントハンドラーを作成します。
チャットボードにコメントを追加し、成功したら状態を更新します。
def handle_event(
"add_comment",
%{"comment" => cmnt},
socket = %{assigns: %{board: board, current_user: user}}
) do
case Meeting.comment_to_board(user, board.id, cmnt) do
{:ok, comment} ->
comments = board |> Meeting.list_comments
{:noreply, assign(socket, comment: LvChat.Meeting.change_comment, comments: comments)}
{:error, _changeset} ->
{:noreply, socket}
end
end
BoardLiveView全体は以下になります。
defmodule LvChatWeb.BoardLiveView do
use Phoenix.LiveView
alias LvChatWeb.BoardView
alias LvChat.Meeting
def render(assigns) do
Phoenix.View.render(BoardView, "show.html", assigns)
end
def mount(session = %{current_user: _user, board: board}, socket) do
assigns =
Map.merge(
session,
%{
comments: board |> Meeting.list_comments,
comment: LvChat.Meeting.change_comment
}
)
{:ok, assign(socket, assigns)}
end
def handle_event(
"add_comment",
%{"comment" => cmnt},
socket = %{assigns: %{board: board, current_user: user}}
) do
case Meeting.comment_to_board(user, board.id, cmnt) do
{:ok, comment} ->
comments = board |> Meeting.list_comments
{:noreply, assign(socket, comment: LvChat.Meeting.change_comment(), comments: comments)}
{:error, _changeset} ->
{:noreply, socket}
end
end
end
フレームワークにLiveViewのテンプレートであることを認識させるために、コマンドで生成されたファイル名の拡張子を eex
から leex
に変更します。
/lib/lv_chat_web/templates/board/show.html.leex
入力フォームでボタン押下時には、phx-submit="add_comment"
を指定します。
<div class="form-group">
<%= f = form_for @comment, "#", [phx_submit: :add_comment] %>
<%= text_input f, :body %>
<%= submit "送信" %>
</form>
</div>
メッセージ表示部には、以下の定義を行います。
定義 | 設定値 | 備考 |
---|---|---|
id | msg-container | 要素へのアクセスに使用します。 |
phx-update | replace | デフォルト値です。古い定義を新しい定義で置き換えます。 |
phx-hook | NewComment | DOMの変化等によりJs側の関数を呼び出すようになります。 |
style | height: 200px; overflow-y: scroll; | インラインスタイルです。 |
# /lib/lv_chat_web/templates/board/show.html.leex
<div
id="msg-container"
phx-update="replace"
phx-hook="NewComment"
style="height: 200px; overflow-y: scroll;"
>
<ul style="list-style: none;">
<%= for comment <- @comments do %>
<li id="<%= comment.id %>"> <%= comment.body %> </li>
<% end %>
</ul>
</div>
全体は以下のようになります。
# /lib/lv_chat_web/templates/board/show.html.leex
<h1>Show Board</h1>
<ul>
<li>
<strong>Name:</strong>
<%= @board.name %>
</li>
<li>
<strong>Description:</strong>
<%= @board.description %>
</li>
</ul>
<div class="form-group">
<%= f = form_for @comment, "#", [phx_submit: :add_comment] %>
<%= text_input f, :body, value: @comment.changes[:body] %>
<%= submit "送信" %>
</form>
</div>
<div id="msg-container" phx-update="replace" style="height: 200px; overflow-y: scroll;" phx-hook="NewComment">
<ul style="list-style: none;">
<%= for comment <- @comments do %>
<li id="<%= comment.id %>"> <%= comment.body %> </li>
<% end %>
</ul>
</div>
投稿が増えてくると、新しい投稿があってもそのままでは表示されなくなります。
表示するようにするためには、投稿するたびに最下端までスクロールをする必要があります。
そのような場合に使用できるのが、phx-hook
です。
この記述により、所定のタイミングでJavaScript側の指定した関数が呼び出されるようになります。
// /assets/js/app.js
let Hooks = {}
const scrollToBottomByElement = (element) => {
if (element) {
element.scrollTop = element.scrollHeight
}
}
Hooks.NewComment = {
mounted() {
scrollToBottomByElement(document.getElementById("msg-container"));
},
updated() {
scrollToBottomByElement(document.getElementById("msg-container"));
// this.pushEvent("some_event",{});
}
}
let liveSocket = new LiveSocket("/live", Socket, { hooks: Hooks })
liveSocket.connect()
これで、LiveViewを使って投稿の表示も行うことができるようになりました。
ここまでで、LiveViewを使って投稿を表示できるようになりました。
ただし、現状ではまだリアルタイムに他のユーザーにコメントを伝搬させることはできません。
そのためには、もう人手間必要になります。
それは次回に記述します。