Facebookメッセンジャー等を見ると、相手がメッセージを入力しているときに入力状況を示すアニメーションが表示されます。
そのような機能を本チャットボードにも追加します。
本チャットボードでは、入力中に入力しているユーザーの横に「(typing...)」という表示を行うようにします。
本テーマは7記事構成になっています。
7記事それぞれで取り扱う内容は以下です。
本テーマは以下の方針で記述します。
完成版の画面イメージは以下のようになっています。
typing
に入力ステータスをもたせることにします。
それを追跡情報に追加します。
ハイライトされている箇所が今回追加したところです。
# /lib/lv_chat_web/views/board_live_view.ex
def mount(session = %{current_user: user, board: board}, socket) do
Presence.track_presence(
self(),
board.id |> topicId,
user.id,
%{
name: user.name,
id: user.id,
typing: false }
)
...
end
入力状態を以下のように定義します。
項目 | 値 |
---|---|
入力中 | inputに変化がある |
入力中ではない | inputからフォーカスが外れている |
実際のイベントとあわせて状態遷移図にすると以下のようになります。
状態遷移図の通りにイベントを指定します。
formとinputそれぞれの要素に対して指定します。
# /lib/lv_chat_web/templates/board/show.html.leex
<div class="form-group">
<%= f = form_for @comment, "#", [phx_submit: :add_comment, phx_change: :typing] %> <%= text_input f, :body, phx_blur: "stop_typing" %> <%= submit "Send" %>
</form>
</div>
入力中の処理です。
タイピングしているユーザーのIDと %{typing: true}
を指定しています。
ポイントとなる箇所をハイライトしています。
# /lib/lv_chat_web/views/board_live_view.ex
def handle_event("typing", _value, socket = %{assigns: %{board: board, current_user: user}}) do
topic = board.id |> topicId
key = user.id
payload = %{typing: true}
Presence.update_presence(self(), topic, key, payload)
{:noreply, socket}
end
ここで update_presence
は以下のように実装しています。
# /lib/lv_chat_web/channels/presence.ex
def update_presence(pid, topic, key, payload) do
metas =
Presence.get_by_key(topic, key)
|> extract_metadata
|> Map.merge(payload)
Presence.update(pid, topic, key, metas)
end
extract_metadata
についてですが、もとのデータは以下のような形で取得されます。
%{metas: [%{id: 1, name: "freddie", phx_ref: "OoxOA/GE9hY=", typing: false}]}
変換後は以下のようになります。
%{id: 1, name: "freddie", phx_ref: "OoxOA/GE9hY=", typing: false}
複数のユーザーがそのチャットボードにアクセスしているときは、リストとして取得されます。
[
%{id: 1, name: "freddie", phx_ref: "OoxOA/GE9hY=", typing: false},
%{id: 2, name: "brian", phx_ref: "aUeSKX6opX0=", typing: false}
]
入力を終了したときの処理です。
タイプング状態をクリアしています。
ポイントとなる箇所をハイライトしています。
# /lib/lv_chat_web/views/board_live_view.ex
def handle_event(
"stop_typing",
%{"value" => val},
socket = %{assigns: %{board: board, current_user: user}}
) do
topic = board.id |> topicId
key = user.id
payload = %{typing: false}
comment = LvChat.Meeting.change_comment(%Meeting.Comment{},%{body: val})
Presence.update_presence(self(), topic, key, payload)
{:noreply, assign(socket, comment: comment)}end
typing
が true
の場合には「(typing...)」と表示するようにしています。
# /lib/lv_chat_web/templates/board/show.html.leex
<h3>Users</h3>
<ul id="user-container">
<%= for user <- @users do %>
<li id="<%= user.phx_ref %>"><%= user.name %><%= if user.typing, do: "(typing...)" %></li> <% end %>
</ul>
全体的には、以下のようになります。
# /lib/lv_chat_web/templates/board/show.html.leex
<div class="form-group">
<%= f = form_for @comment, "#", [phx_submit: :add_comment, phx_change: :typing] %> <%= text_input f, :body, phx_blur: "stop_typing" %> <%= submit "Send" %>
</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.user.name %> : <%= comment.body %> </li>
<% end %>
</ul>
</div>
<h3>Users</h3>
<ul id="user-container">
<%= for user <- @users do %>
<li id="<%= user.phx_ref %>"><%= user.name %><%= if user.typing, do: "(typing...)" %></li> <% end %>
</ul>
これで入力ステータスの表示は完了です。
一連の記事で、Phoenix.LiveView を用いたそこそこ実用的なアプリを作りました。
ほぼ JavaScript のコードを書くことなく(一部DOM操作で書いた)、ElixirのみでリアルタイムなWebアプリを作成できるのは便利ですね。
ちなみに、今回は見た目的には全く考慮していませんのであしからず。
一連のソースコードは以下に置いてあります。