Phoenix LiveView で キーボードイベントをひろうプログラムです。LiveView と JavaScript Hook を組み合わせたものとなります。クライアント要素の状態管理やロジックは Elixir 側で行い、JavaScript では単にキーボードイベントを拾い Elixir に渡すだけのシンプルなものとなります。Elixir 側で状態を更新することで、LiveView 画面が自動更新されます。
プログラムの全体像やポイントは、箇条書き的に書くと以下のようになります。
- ブラウザ上のオブジェクトをキーボード操作で上下左右に動かす
- オブジェクトはdiv要素とし、その絶対座標を動的に変更することで動作させる
- キーボードのイベントはJavaScriptの window.addEventListener() を利用する
- JavaScriptからElixirへはJavaScript Hook の life-cycle callbacks メソッドの pushEvent() を使う
- イベントを受け取った Elixir 側では押されたキーに従って座標を更新する
- 更新された座標は Phoenix.PubSub で全ブラウザに通知される
- 各々のブラウザの画像は、LiveView のメカニズムにより、更新された座標で自動更新される。
【関連記事】
Phoenix LiveView と キーボードイベント - Qiita
Phoenix LiveView の JavaScript Hook - Qiita
Phoenix LiveViewの基本設定 - Qiita
Phoenix1.6の基本的な仕組み - Qiita
実行環境
Elixir 1.13.0
Phoenix 1.6.12
1.JavaScript Hookの設定
mix phx.new liveview_keyboard --no-ecto --no-mailer --no-dashboard
cd liveview_keyboard
まずはクライアントのJavaScript Hook の設定から行います。以下、Hookの完全な説明は以下のドキュメントを参照してください。
Client hooks via phx-hook - JavaScript interoperability
keyboard_hook.jsでは hooks.keyboard という名前で Hook object を設定します。これを受けて、後述するphx-hook の指定は phx-hook="keyboard" となります。これはキーボードイベントを拾ってElixirに送るプログラムです。life-cycle callbacksメソッドの pushEvent() の利用に注意してください。
let hooks = {};
hooks.keyboard={
mounted(){
window.addEventListener("keydown", e => {
console.log(e.key)
this.pushEvent("key_down", {key: e.key})
});
}
}
export default hooks
app.js に Hook を設定します。
import keyboard_hooks from './keyboard_hook'
---
let liveSocket = new LiveSocket("/live", Socket,
{params: {_csrf_token: csrfToken}, hooks: keyboard_hooks})
2.LiveView controller の作成
まずは慣例に従って live ディレクトリを作成します。
mkdir lib/liveview_keyboard_web/live
renderのhtmlを作成します。親要素のdivを position relativeで作成し、子要素のdivをposition absoluteで作成します。これで子要素のx座標、y座標を left、top で指定することができるようになります。leftとtopの値をelixir側で動的に変更することで、この子要素を動作させることができます。
ここでphx-hook の指定を忘れずに行います。
<style>
.absolute_parent {
position: relative;
left: 30px;
top: 30px;
border: solid 2px red;
height: 600px;
width: 800px;
}
.absolute_child {
position: absolute;
background-color: #ccc;
padding: 5px;
border: solid 5px black;
}
</style>
<div id="myparent" class="absolute_parent" phx-hook="keyboard">
<div id="mykeyboard" class="absolute_child" style={@style}>
Me
</div>
</div>
ちょっと見やすい画面にするために root.html.heex の body を以下のものに置き換えます。
<body>
<header>
<section class="container">
<h1>LiveView Keyboard Example</h1>
</section>
</header>
<%= @inner_content %>
</body>
最後に LiveView controller を作成します。私的には、LiveView では JavaScript側はできるだけシンプルなコードにして、複雑な状態管理やロジックはElixir側で取り込む方針でできればと考えています。今回もJavaScriptやHtmlはとてもシンプルになっており、ほとんどの処理はこの LiveView controller で書いているといっても過言ではありません。
キーボードイベントの識別と、それによるdiv要素のx座標、y座標の更新を行っています。x座標、y座標は socket.assigns で管理します。CSSのstyleもElixirで作成しクライアントに渡します。クライアント側はそれをそのまま表示しているだけです。
defmodule LiveviewKeyboardWeb.KeyboardLive do
use LiveviewKeyboardWeb, :live_view
alias Phoenix.PubSub
def mount(_params, _session, socket) do
if connected?(socket) do
PubSub.subscribe(LiveviewKeyboard.PubSub, "liveview_keyboard")
end
x_pos = 300
y_pos = 100
style = make_style(x_pos, y_pos)
socket = assign(socket, style: style, x_pos: x_pos, y_pos: y_pos)
{:ok, socket}
end
def handle_info(%{style: style , x_pos: x_pos , y_pos: y_pos}, socket) do
socket = assign(socket, style: style, x_pos: x_pos, y_pos: y_pos)
{:noreply, socket}
end
def handle_event("key_down", %{"key" => key}=params, %{assigns: assigns}=socket) do
%{x_pos: x_pos, y_pos: y_pos} = assigns
{x_pos, y_pos} =
case key do
"w"-> {x_pos, y_pos - 10}
"s"-> {x_pos, y_pos + 10}
"a"-> {x_pos - 10, y_pos}
"d"-> {x_pos + 10, y_pos}
_-> {x_pos, y_pos}
end
style = make_style(x_pos, y_pos)
PubSub.broadcast(LiveviewKeyboard.PubSub, "liveview_keyboard",
%{style: style , x_pos: x_pos , y_pos: y_pos} )
{:noreply, socket}
end
defp make_style(x_pos, y_pos) do
"left: " <> Integer.to_string(x_pos) <> "px; top: " <>
Integer.to_string(y_pos) <> "px;"
end
end
以下、もう少し詳細なコードの説明です。Phoenix.PubSub については「Phoenix.PubSub ドキュメント」 を参照。
- mount() は2度呼ばれますが、2度目にsocketがつながった状態で呼ばれます。この時、PubSub.subscribe() で指定トピックに参加します。また初期値のx座標、y座標に基づいたstyleを作成し、socket.assigns に保存します。
- handle_event() はクライアント側のJavaScriptから送られたイベントを受け取ります。ここで送られたイベントデータを元に新しいx座標、y座標を計算し、それに基づいた新しいstyleを作ります。これらの値は PubSub.broadcast() でトピックに参加している全員にブロードキャストされます。
- handle_info() ではブロードキャストされた値を受け取り、それぞれ socket.assigns を更新します。後は更新された値を {:noreply, socket} でブラウザに反映させます。
3.動作画像
サーバを起動します。
mix phx.server
ブラウザを2つ立ち上げ、それぞれ http://localhost:4000/ にアクセスします。2つのブラウザに同じ初期状態画面が表示されます。
片方のブラウザにフォーカスを合わせ、キー入力で右下に移動させます。別のブラウザでも、自動的に連動して右下に移動していくのが観測できます。
うまく動作しました。
今回は以上です。