4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Phoenix LiveView の JavaScript Hook

Last updated at Posted at 2022-10-14

Phoenix LiveViewのJavaScript Hook の設定記事です。chartjsで簡単なチャートを描くプログラムですサーバ側で生成したデータをリアルタイムにクライアント側のチャートに反映させます。
JavaScript interoperability

【関連記事】
Phoenix LiveView と キーボードイベント - Qiita
Phoenix LiveView の JavaScript Hook - Qiita
Phoenix LiveViewの基本設定 - Qiita
Phoenix1.6の基本的な仕組み - Qiita

実行環境
Elixir 1.13.0
Phoenix 1.6.12

1.LiveViewの基本設定

まずプロジェクトを作成します。

mix phx.new liveview_chart --no-ecto --no-mailer --no-dashboard
cd liveview_chart

次に慣例に従ってliveディレクトリを作成します

mkdir lib/liveview_chart_web/live

LiveView controllerchart_live.ex という名前で作成します。

lib/liveview_chart_web/live/chart_live.ex
defmodule LiveviewChartWeb.ChartLive do
  use LiveviewChartWeb, :live_view

  def mount(_params, _session, socket) do
    {:ok, socket}
  end
end

ここではrender の明示的な設定は行わず chart_live.html.heex という名前を合わせたファイルを作ることで暗示的に行います。

lib/liveview_chart_web/live/chart_live.html.heex
<h1>LiveView Chart Page</h1>

root.html.heex の body を以下のもので置き換えます。

lib/liveview_chart_web/templates/layout/root.html.heex
  <body>
    <header>
      <section class="container">
        <h1>LiveView Chart Example</h1>
      </section>
    </header>
    <%= @inner_content %>
  </body>

router.ex パスを修正して、"/"パスを LiveView controller を指すようにします。

lib/liveview_chart_web/router.ex
  scope "/", LiveviewChartWeb do
    pipe_through :browser

    live "/", ChartLive
  end

基本設定が終わったのでサーバを起動します。

mix phx.server

http://localhost:4000/ にアクセスします。

image.png

成功ですね

2.JavaScript Hook

これからは実際にchartjsを実装し、サーバから送られるイベントに従ってチャートをリアルタイムに更新していくことを考えていきます。

chart.jsをインストールします。

npm install --save --prefix assets chart.js

以下、Hookの説明が続きますが、完全な説明は以下のドキュメントを参照してください。
Client hooks via phx-hook - JavaScript interoperability

phx-hook

chartを描くためのcanvasを用意するために、chart_live.html.heexを以下のように修正します。ここでcanvasのstyleも定義しておきます。phx-hook="chart"Hook object を指定しています。あとでchart_hook.js を定義しますが、そこで hooks.chart という名前で Hook object を定義します。そこではmounted というlife-cycle callbacksが定義されています。

lib/liveview_chart_web/live/chart_live.html.heex
<style>
  #chart-wrapper {
    display: inline-block;
    position: relative;
    width: 100%;
  }
</style>
<div id="chart-wrapper">
  <canvas id="myChart" phx-hook="chart"></canvas>
</div>

life-cycle callbacks

phx-hook で指定された Hook object は以下のような life-cycle callbacks を持ちます。

  • mounted - DOM 要素が追加され、LiveView で mount が終了したときに呼ばれる
  • updated - サーバによってDOM要素が update されたときに呼ばれる
  • destroyed - DOM要素がページから削除されたとき(親要素のupdateや親要素全体の削除によって)

life-cycle callbacks の属性

life-cycle callbacks からは以下のような属性にアクセスできます。

  • el - Hook object が束縛された DOM要素への参照
  • pushEvent(event, payload, (reply, ref) => ...) - client から the LiveView server へイベントをpushするためのメッソド
  • handleEvent(event, (payload) => ...) - server から push されたイベントをハンドルするためのメッソド

chart_hook.js では Hook objecthooks.chart を定義し、chartjsを用いて初期状態のチャートを描いています。また this.handleEvent() で、サーバ側から"points"イベントが送信されたときのイベントハンドラーを設定しています。
また this.el は canvas の要素を指していることに注意してください。

assets/js/chart_hook.js
import Chart from 'chart.js/auto';

const labels = [
  'January',
  'February',
  'March',
  'April',
  'May',
  'June',
];

const data = {
  labels: labels,
  datasets: [{
    label: 'My First dataset',
    backgroundColor: 'rgb(255, 99, 132)',
    borderColor: 'rgb(255, 99, 132)',
    data: [0, 10, 5, 2, 20, 30, 45],
  }]
};

const config = {
  type: 'line',
  data: data,
  options: {}
};

let hooks = {}
hooks.chart = {
    mounted() {
        var ctx = this.el
        var chart = new Chart(ctx, config)
        this.handleEvent("points", ({points}) => {
            chart.data.datasets[0].data = points
            chart.update()
        })
    }
}

export default hooks

app.jsでは上で定義したhookを読み込んで、LiveSocketに設定します。

assetss/js/app.js
---
import chart_hooks from './chart_hook';  // 追加

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")

let liveSocket = new LiveSocket("/live", Socket, 
    {params: {_csrf_token: csrfToken}, hooks: chart_hooks})  // 修正
---

LiveView controller のchart_live.exを以下のように変更します。
mountは2度呼ばれますが、2度目にsocketがつながった状態で呼ばれます。この時、2秒おきに自分にupdate_chartイベントを送信するタイマーを設定します。

lib/liveview_chart_web/live/chart_live.ex
defmodule LiveviewChartWeb.ChartLive do
  use LiveviewChartWeb, :live_view

  @impl true
  def mount(_params, _session, socket) do
    if connected?(socket) do
      :timer.send_interval(2000, self(), :update_chart)
    end
    {:ok, socket}
  end

  @impl true
  def handle_info(:update_chart, socket) do
    socket = push_event(socket, "points", %{points: get_points})
    {:noreply, socket}
  end

  defp get_points, do: 1..6 |> Enum.map(fn _ -> :rand.uniform(100) end)
end

設定が終わったのでサーバを起動します。

mix phx.server

http://localhost:4000/ にアクセスします。

image.png

2秒おきにグラフが更新されるのを確認できます。

push_event について

ここで push_event の動作を知るために以下のようにデバッグプリントを入れました。

    IO.inspect(socket |> Map.from_struct())
    socket = push_event(socket, "points", %{points: get_points})
    IO.inspect(socket |> Map.from_struct())

push_event の前後でsocket.private.__changed__の値を見ると、
push_event前

 %{},

push_event後

%{ push_events: [["points", %{points: [14, 54, 16, 85, 93, 21]}]] },

最後に、この情報が {:noreply, socket} でクライアントに送られているようです。

今回は以上です。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?