PhoenixLiveViewの0.2.0-devベースでカウンタを作ってみた

JavaScriptを記述しないでSPA（シングルページアプリケーション）のような操作ができるPhoenixのライブラリ「 **Phoenix LiveView** 」ですが、GitHubページのCHANGELOGを見ていると2019年8月末あたりからバージョニングされていたのに気づきました。

https://github.com/phoenixframework/phoenix_live_view/blob/master/CHANGELOG.md

せっかくバージョンが0.1系から0.2系に変わっていたのに気づいたので、興味本位で少し触ってみました。

## はじめに

Phoenix LiveViewを利用して、ボタンクリックによって数値numの増減とともに、その数値をベースに計算結果（FizzBuzzの判定とフィボナッチ数の算出）を返す画面を作成しました。

操作イメージは、以下のGif画像を参照ください。
![LiveView_0.2.0.dev_2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316636/9cc89e0f-8add-c5cb-fb75-83ee1b993c54.gif)


外側のボタンでは値を増減した後で計算結果も算出します。
一方、内側のボタンは計算をしないでベースになる値だけを増減させます。
真ん中のボタンは全ての数値を0に戻します。

ベースになる実装内容については、こちらの記事を参考にしています。
https://qiita.com/kikuyuta/items/c5b0788ad5d09c210c0a

### 補足事項

Phoenix LiveViewはバージョンアップも頻繁に行われていますので、もしかしたら今回の内容で動作できなくなる場合もあります。
修正箇所に気づいたら、できる限り記事の更新は行う予定です。

## 環境構築について

Phoenixのプロジェクト名については「 **hiyoko** 」としています。
そのため、パス名やモジュール名は「 **hiyoko_web** 」や「 **HiyokoWeb** 」のような名称になっています。
もし、ご自身のプロジェクトで実施させる際には、プロジェクト名に由来する箇所は読み替えてください。

環境構築についても、先ほど挙げたこちらの記事を参考にしています。

https://qiita.com/kikuyuta/items/c5b0788ad5d09c210c0a

ただし、0.2.0-devへのバージョンアップに伴い、「assets/js/app.js」に対して **後方互換のない変更** が発生していました。以前の内容と比べて、以下の点で変更が必要です。

 * 「`import {Socket} from "phoenix"`」 の追記
 * 「`LiveSocket("/live", Socket)`」のように第二引数へ「Socket」を追加

```javascript:assets/js/app.js
import {Socket} from "phoenix"
import LiveSocket from "phoenix_live_view"

let liveSocket = new LiveSocket("/live", Socket)
liveSocket.connect()
```

## コードの記述

Elixir側で記述したコードは、以下の内容です。
ボタンクリック（phx-click）の種類別に、 **handle_event** 関数を定義しています。
特定の値だけを更新したい場合には **update**を、更新したい値を多く渡したい場合は **assign** を呼び出しています。

なお「 **phx-click** 」についてですが、こちらもLiveViewのバージョン0.1.2で **後方互換のない変更** が発生しています。
0.1.1までは「`phx-value=1`」のような形式でしたが、0.1.2以降では「`phx-value-*`」形式になっています。handle_event内での値の取り出し方も「\*」に当たる部分を指定（例えば、「`phx-value-decv`」の場合、`v["decv"]`のように指定）して値を取り出すようになりました。


```elixir:lib/hiyoko_web/router.ex
〜（略）〜
  scope "/", HiyokoWeb do
    pipe_through :browser

    get "/", PageController, :index
    live "/exercises", Exercises      #<- ここを追記
  end
〜（略）〜
```

```elixir:lib/hiyoko_web/live/exercises.ex
defmodule HiyokoWeb.Exercises do
  use Phoenix.LiveView

  def render( assigns ) do
    ~L"""
      <h2>FizzBuzz  Fibonacci</h2>
      <p>
        <%= @message %>
      </p>
      <div>
        <h3>The count is: <%= @num %></h1>
        <button phx-click="deccalc" phx-value-decv=1>-(calc)</button>
        <button phx-click="dec" phx-value-decv=3>-</button>
        <button phx-click="clear" phx-value-clear=0>clear</button>
        <button phx-click="inc" phx-value-incv=4>+</button>
        <button phx-click="inccalc" phx-value-incv=1>+(calc)</button>
      </div>
      <table>
      <tr>
        <th>num</th>
        <th>FizzBuzz</th>
        <th>Fibonacci</th>
      </tr>
      <tr>
        <td><%= @num %></td>
        <td><%= @fzbz %></td>
        <td><%= @fibo %></td>
      </tr>
      </table>
    """
  end

  def mount( _session, socket ) do
    {:ok, assign( socket, message: "", num: 0, fzbz: 0, fibo: 0 ) }
  end

  def handle_event("inc", _, socket) do
    {:noreply, update(socket, :num, &(&1 + 1))}
  end

  def handle_event("dec", v, socket) do
    {:noreply, update(socket, :num, &(&1 - String.to_integer(v["decv"])))}
  end


  def handle_event("inccalc", v, socket) do
    add = fn(x, y) -> x + y end
    v1 = socket.assigns.num |> add.(String.to_integer(v["incv"]))
    {:noreply, assign(socket, message: "Calculation(add)", num: v1, fzbz: fizzbuzz(v1), fibo: fib(v1))}
  end

  def handle_event("clear", _, socket) do
    {:noreply, assign(socket, message: "Clear", num: 0, fzbz: 0, fibo: 0)}
  end

  def handle_event("deccalc", v, socket) do
    subtract = fn(x, y) -> x - y end
    v1 = socket.assigns.num |> subtract.(String.to_integer(v["decv"]))
    {:noreply, assign(socket, message: "Calculation(subtract)", num: v1, fzbz: fizzbuzz(v1), fibo: fib(v1))}
  end


  defp fizzbuzz(x) do
    fb = fn
      0, 0, _ -> "FizzBuzz"
      0, _, _ -> "Fizz"
      _, 0, _ -> "Buzz"
      _, _, i -> Integer.to_string(i)
    end
    fb.(rem(x,3), rem(x,5), x)
  end


  defp fib(0), do: 0
  defp fib(1), do: 1
  defp fib(x) when (x >=2) do
    fib(x-1) + fib(x-2)
  end
  defp fib(x) when (x < 0) do
    ax = abs(x)
    :math.pow(-1, ax+1) * fib(ax)
  end
end
```

実際に書いてみるとわかりますが、JavaScriptのコードを直接書かずにSPAのようなことができるのは、ちょっとした感動を与えてくれます。
フロントエンドのコードを意識せずに、サーバサイドだけで完結できるのは随分とお手軽です。

## 最後に宣伝

技術書典7(2019月9月22日)で、「 **PhoenixLiveViewとNervesをさわるElixirへのいざない** 」という本を頒布します。

https://techbookfest.org/event/tbf07/circle/5173194898014208

タイトルの通り、Elixir界隈では最近注目の的である「 **Phoenix LiveView** 」と「 **Nerves** 」という二つのフレームワークについてさわってみようという趣旨の入門本となっています。

興味を持たれたら、ぜひお立ち寄りください。

執筆時期の関係上、LiveViewのバージョンについて「 **0.1.1** 」ベースの内容になっています。
LiveViewのバージョンを明示的に **0.1.1** に指定したい場合は、以下のようにすれば良いです。

```elixir:mix.exs
{:phoenix_live_view, github: "phoenixframework/phoenix_live_view"},
```

の箇所を

```elixir:mix.exs
{:phoenix_live_view, "0.1.1"},
```
に修正。