Elixir＆Phoenix＆LiveViewでカウンター作成を試してみた。

以下の動画で作成しているカウンターを実際に試してみたのでメモします。
すこし古い動画だったため、そのままでは動かず苦労しました。
何とか動かせたので、それらを踏まえてメモします。

https://www.youtube.com/watch?v=U_Pe8Ru06fM&t=745s

以下のバージョンを使用しました。
Elixir：1.12.1
Phoenix：v1.5.9


# プロジェクト作成
以下のコマンドで新しいプロジェクトを作成します。

```
$ mix phx.new counter --no-ecto

# 途中、以下のように応答を求められるので「y」を応答する。
Fetch and install dependencies? [Yn] y

# プロジェクトフォルダに移動。
$ cd counter
```

今回はデータベースを使用しないため`--no-ecto`オプションを付けています。

次の手順「LiveViewのインストール」は`--live`オプションを付けてプロジェクトを作成している場合は不要です。
今回は動画通りに進めるため、`--live`オプションを使用せずにプロジェクトを作成しました。

# LiveViewのインストール
以下を参考にインストールします。

https://hexdocs.pm/phoenix_live_view/installation.html

## パッケージ追加インストール

LiveView用のパッケージを追加します。
(# 追加はただのコメントです。実際には記入しません。後続のコードでも同様に不要です。)

```elixir:mix.exs
def deps do
  [
    ...
    {:phoenix_live_view, "~> 0.15.0"},        # 追加
    {:floki, ">= 0.30.0", only: :test}        # 追加
  ]
end
```

ドキュメントでは`{:phoenix_live_view, "~> 0.16.3"}`となっていますが、
依存関係の問題で失敗するため、バージョンを`~> 0.15.0`にしています。

以下を実行してインストールします。

```bash:インストール
$ mix deps.get
```

## signing salt設定
signing saltの設定は桁数が違いますが、既に設定されていたので飛ばします。
設定内容についてはドキュメントを参照してください。

## import追加
`lib/counter_web.ex`にimportを追加します。

```elixir:lib/counter_web.ex
def view do
  quote do
    ...
    import Phoenix.LiveView.Helpers        # 追加
  end
end

def router do
  quote do
    ...
    import Phoenix.LiveView.Router        # 追加
  end
end
```


## fetch_flashの設定
`lib/counter_web/router.ex`のbrowser pipelineの設定を変更します。

```elixir:lib/counter_web/router.ex
pipeline :browser do
  ...
  plug :fetch_flash            # 削除
  plug :fetch_live_flash       # 追加
end
```

## socketの設定
socketの設定も既に設定されていたので飛ばします。
設定内容についてはドキュメントを参照してください。

## npm dependencies追加

`assets/package.json`にphoenix_live_viewを追加します。

```json:assets/package.json
...
  "dependencies": {
    "phoenix": "file:../deps/phoenix",
    "phoenix_html": "file:../deps/phoenix_html",
    "phoenix_live_view": "file:../deps/phoenix_live_view"        # 追加
  },
...
```

以下を実行しインストールします。

```bash:インストール
$ npm install --prefix assets
```

## Progress animation追加
以下を実行してインストールします。

```
npm install --prefix assets --save topbar
```

## jsファイルの修正
以下の内容を`assets/js/app.js`に追記します。

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

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})

// Show progress bar on live navigation and form submits
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
window.addEventListener("phx:page-loading-start", info => topbar.show())
window.addEventListener("phx:page-loading-stop", info => topbar.hide())

// Connect if there are any LiveViews on the page
liveSocket.connect()

// Expose liveSocket on window for web console debug logs and latency simulation:
// >> liveSocket.enableDebug()
// >> liveSocket.enableLatencySim(1000)
// The latency simulator is enabled for the duration of the browser session.
// Call disableLatencySim() to disable:
// >> liveSocket.disableLatencySim()
window.liveSocket = liveSocket
```

##layout用ファイルの追加修正
layout用のファイルとして以下の3つのファイルを追加修正する。
・lib/counter_web/templates/layout/root.html.leex
・lib/counter_web/templates/layout/app.html.eex
・lib/counter_web/templates/layout/live.html.leex

###lib/counter_web/templates/layout/root.html.leex を追加
root.html.eexは通常用テンプレート、LiveView用テンプレートが読み込まれるテンプレートになります。

```eex:lib/counter_web/templates/layout/root.html.leex
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <%= csrf_meta_tag() %>
    <title>Counter · Phoenix Framework</title>
    <link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
    <script defer type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
  </head>
  <body>
    <header>
      <section class="container">
        <nav role="navigation">
          <ul>
            <li><a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a></li>
            <%= if function_exported?(Routes, :live_dashboard_path, 2) do %>
              <li><%= link "LiveDashboard", to: Routes.live_dashboard_path(@conn, :home) %></li>
            <% end %>
          </ul>
        </nav>
        <a href="https://phoenixframework.org/" class="phx-logo">
          <img src="<%= Routes.static_path(@conn, "/images/phoenix.png") %>" alt="Phoenix Framework Logo"/>
        </a>
      </section>
    </header>
    <%= @inner_content %>
  </body>
</html>
```

###lib/counter_web/templates/layout/app.html.eex を修正

<header>タグなどはroot.html.eexに記述しているためapp.html.eexではmainのみになっています。
app.html.eexは通常のLiveViewではないページに使用されるテンプレートになります。

```eex:lib/counter_web/templates/layout/app.html.eex
<main role="main" class="container">
  <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
  <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
  <%= @inner_content %>
</main>
```

###lib/counter_web/templates/layout/live.html.leex を追加
<header>タグなどはroot.html.eexに記述しているためlive.html.leexではmainのみになっています。
live.html.leexはLiveViewのページに使用されるテンプレートになります。

```leex:lib/counter_web/templates/layout/live.html.leex
<main role="main" class="container">
  <p class="alert alert-info" role="alert"
    phx-click="lv:clear-flash"
    phx-value-key="info"><%= live_flash(@flash, :info) %></p>

  <p class="alert alert-danger" role="alert"
    phx-click="lv:clear-flash"
    phx-value-key="error"><%= live_flash(@flash, :error) %></p>

  <%= @inner_content %>
</main>
```

###put_root_layout追加
lib/counter_web/router.exのbrowser pipelineに追記します。

```elixir:lib/counter_web/router.ex
pipeline :browser do
  ...
  plug :put_root_layout, {CounterWeb.LayoutView, :root}        # 追加
  ...
end
```


##phx.gen.liveサポート
Phoenix v1.5から追加されたphx.gen.liveを使用できるように、
`lib/counter_web.ex`に`live_view`関数と`live_component`関数を追加します。

```elixir:lib/counter_web.ex
  def view do
    ...
  end

  def live_view do
    quote do
      use Phoenix.LiveView,
        layout: {CounterWeb.LayoutView, "live.html"}

      unquote(view_helpers())
    end
  end

  def live_component do
    quote do
      use Phoenix.LiveComponent

      unquote(view_helpers())
    end
  end
...
```

以上でインストール作業は終わりです。
次の項目からが動画で実施している内容です。

# ルート追加
`lib/counter_web/router.ex`にルートを追加します。

```elixir:lib/counter_web/router.ex
  ...
  scope "/", CounterWeb do
    pipe_through(:browser)

    get("/", PageController, :index)

    live("/counter", CounterLive)        # 追加
  end
  ...
```


# CounterLive追加
lib/counter_web/live/counter_live.exファイルを作成し、各関数を定義します。
(liveフォルダも新規で作成しています。)

```elixir:lib/counter_web/live/counter_live.ex
defmodule CounterWeb.CounterLive do
  use Phoenix.LiveView, layout: {CounterWeb.LayoutView, "live.html"}

  def mount(_params, _session, socket) do
    socket = assign(socket, :count, 0)
    {:ok, socket}
  end

  def render(assigns) do
    ~L"""
    <h1>Count: <%= @count %></h1>
    <button phx-click="increment">+</button>
    <button phx-click="decrement">-</button>
    """
  end

  def handle_event("increment", _, socket) do
    count = socket.assigns.count + 1
    socket = assign(socket, :count, count)
    {:noreply, socket}
  end

  def handle_event("decrement", _, socket) do
    count = socket.assigns.count - 1
    socket = assign(socket, :count, count)
    {:noreply, socket}
  end
end
```

live.html.leexを読み込ませるために、`layout: {CounterWeb.LayoutView, "live.html"}`を追記しています。

動画では`mount/2`を定義していますが、そのままだとエラーになるため、かわりに`mount/3`を定義しています。
参考：https://elixirforum.com/t/liveview-argumenterror-assign-count-not-available-in-eex-template/30580/10


# サーバー起動、動作確認
以下のコマンドを実行しサーバーを起動します。

```
$ mix phx.server
```

以下のURLにアクセスして確認します。
http://localhost:4000/counter

![elixr_phoenix_counter.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200585/624aaf24-2ab3-8c66-2f88-359f0a0caa6d.gif)


# おわり
一先ずエラーを解消して、カウンターを作ることができました。

今回は以上です。


#参考
https://www.youtube.com/watch?v=U_Pe8Ru06fM&t=745s

https://hexdocs.pm/phoenix_live_view/installation.html

https://elixirforum.com/t/liveview-argumenterror-assign-count-not-available-in-eex-template/30580/10

https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#c:mount/3

https://www.poeticoding.com/layouts-and-liveeex-templates/

https://github.com/phoenixframework/phoenix_live_view/issues/277