LoginSignup
6
1

More than 3 years have passed since last update.

Phoenix LiveViewのCollocating templates(render/1の省略)

Posted at

Phoenix LiveView、楽しいですね。

Phoenix LiveViewの0.10.0で追加された新機能として「Collocating templates」があります。

これは、LiveViewで利用するテンプレートを「.html.leex」ファイルとして外出しした際に、特定の条件下であればrender/1による明示的なテンプレートの宣言を省略できる機能です。

具体的な条件としては、以下になります。

  1. LiveView用のexファイル中で、モジュール名の末尾にLiveが含まれていること。(例:HelloWeb.FizzbuzzLive
  2. LiveView用のexファイルで定義したモジュール名から変換できる「html.leex」のファイル名であること(例:HelloWeb.FizzbuzzLiveの場合、fizzbuzz_live.html.leexとなる)
  3. テンプレートファイルが、LiveView用のexファイルと同じディレクトリにあること

Collocating templatesに関する詳細はこちらを参照。
https://hexdocs.pm/phoenix_live_view/0.12.1/Phoenix.LiveView.html#module-collocating-templates

動作を確認したバージョン

Phoenix LiveViewはバージョンによる差異が大きいため、動作確認したバージョンを記載しておきます。

  • Elixir 1.10.3
  • Phoenix 1.5.1
  • Phoenix LiveView 0.12.1

Collocating templatesを試す

それでは、Collocating templatesの機能を試してみます。
違いがわかるように、render/1~Lで直接テンプレートを記載した状態からCollocating templatesで動作するように修正してみます。

なお、ベースとなるソースコードは、こちらの記事のコードを利用しています。

通常のrender/1を利用した場合

モジュール名:HelloWeb.Fizzbuzzおよびrender/1を定義したLiveView用の処理を「fizzbuzz.ex」として用意します。

lib/hello_web/live/fizzbuzz.ex
defmodule HelloWeb.Fizzbuzz do
  use Phoenix.LiveView

  import Calendar.Strftime

  def render( assigns ) do
    ~L"""
    <h2>ここに現在時刻が表示されます。</h2>
    <span><%= @time %></span>
    <h2>ここにFizzBuzzの値が表示されます。</h2>
    <span><%= @num %>:<%= @val %></span>
    """
  end

  def mount(_params, _session, socket ) do
    if connected?(socket) do
      Process.send_after(self(), :tick, 1000)
      Process.send_after(self(), :fizzbuzz, 3000)
    end
    { :ok, assign( socket,
                   time: local_time(),
                   num: 0,
                   val: "initial" ) }
  end

  def handle_info(:tick, socket) do
    Process.send_after(self(), :tick, 1000)
    {:noreply, assign(socket, time: local_time())}
  end

  def handle_info(:fizzbuzz, socket) do
    Process.send_after(self(), :fizzbuzz, 3000)
    next_num = socket.assigns.num + 1
    {num, val} = fizzbuzz(next_num)
    {:noreply, assign( socket, num: num, val: val ) }
  end

  ##### local

  defp local_time() do
    :calendar.local_time() |> strftime!("%r")
  end

  defp calc_fb({n, 0, 0}), do: {n, "FizzBuzz"}
  defp calc_fb({n, 0, _}), do: {n, "Fizz"}
  defp calc_fb({n, _, 0}), do: {n, "Buzz"}
  defp calc_fb({n, _, _}), do: {n, to_string(n)}

  defp fizzbuzz(n) do
    cond do
      n < 1 -> {n , "Cannot calculate"}
      true  -> calc_fb({n, rem(n,3), rem(n, 5)})
    end
  end
end

「router.ex」ファイルに、アクセスするURLパスと対応するモジュール名を宣言します。
今回はlive "/fizzbuzz", Fizzbuzzとしています。

lib/hello_web/router.ex(抜粋)
  scope "/", HelloWeb do
    pipe_through :browser

    live "/", PageLive, :index

    live "/fizzbuzz", Fizzbuzz
  end

なお、この機能ではCalendar.Strftimeを利用しているので、mix.exsに対して:calendarの宣言も忘れずに追加しておきます。

mix.exs(抜粋)
  defp deps do
    [
      {:phoenix, "~> 1.5.1"},
      {:phoenix_live_view, "~> 0.12.0"},
      〜(中略)〜
      {:jason, "~> 1.0"},
      {:calendar, "~> 0.17.6"},  # <- これを追記
      {:plug_cowboy, "~> 2.0"}
    ]
  end

ここまでのファイルをPhoenixプロジェクト作成して、iex -S mix phx.serverを実施すれば、Phoenixが起動します。
起動後、localhost:4000/fizzbuzzへアクセスすれば、LiveViewの機能で動く画面が表示されます。

スクリーンショット 2020-05-11 15.31.31.png

Collocating templatesを利用する場合

これを、Collocating templatesを利用したコードに修正します。
変更点は以下の3点です。

  1. 「lib/hello_web/live/fizzbuzz.ex」のモジュール名変更とrender/1の削除(修正
  2. 「lib/hello_web/live/fizzbuzz_live.html.leex」の作成(新規
  3. 「lib/hello_web/router.ex」のアクセスパスのモジュール名変更(修正

まず、モジュール名を「HelloWeb.Fizzbuzz」から「HelloWeb.FizzbuzzLive」へ変更します。
つづいて、関数render/1を関数定義ごとバッサリと削除します。

fizzbuzz.exに対しての修正はこれで終わりです。

lib/hello_web/live/fizzbuzz.ex(抜粋)
defmodule HelloWeb.FizzbuzzLive do
  use Phoenix.LiveView

  import Calendar.Strftime

  def mount(_params, _session, socket ) do
    if connected?(socket) do
      Process.send_after(self(), :tick, 1000)
      Process.send_after(self(), :fizzbuzz, 3000)
    end
    { :ok, assign( socket,
                   time: local_time(),
                   num: 0,
                   val: "initial" ) }
  end

〜(以下略)〜

end

つづいて、fizzbuzz.exで削除したテンプレート内容を持ったファイルを新規に作成します。
ファイル名は、モジュール名をキャメルケースからスネークケースへ変換した名前となります。
今回の場合では、モジュール名がHelloWeb.FizzbuzzLiveであり、FizzbuzzLiveをキャメルケースからスネークケースにしたfizzbuzz_liveとなります。
これに拡張子「.html.leex」を付与した「fizzbuzz_live.html.leex」がファイル名となります。

ファイルの中身については、「fizzbuzz.ex」のrender/1へ定義していたテンプレート内容と同じものを記述します。

lib/hello_web/live/fizzbuzz_live.html.leex
    <h2>ここに現在時刻が表示されます。</h2>
    <span><%= @time %></span>
    <h2>ここにFizzBuzzの値が表示されます。</h2>
    <span><%= @num %>:<%= @val %></span>

さいごに、「router.ex」中に定義した、モジュール名を修正します。
/fizzbuzzに対して設定したモジュール名FizzbuzzFizzbuzzLiveに変更します。

lib/hello_web/router.ex(抜粋)
  scope "/", HelloWeb do
    pipe_through :browser

    live "/", PageLive, :index

    live "/fizzbuzz", FizzbuzzLive, :index
  end

修正完了後、iex -S mix phx.serverの実施後、localhost:4000/fizzbuzzへアクセスすれば、LiveViewの機能で動く画面が表示されます。

スクリーンショット 2020-05-11 20.46.10.png

余談:モジュール名とファイル名とrouter.exでの定義の関係について

fizzbuzz.exで定義したモジュール名をHelloWeb.FizzbuzzからHelloWeb.FizzbuzzLiveへ変更していますが、これはrouter.ex中での定義方法が関係しています。

たとえば、live "/fizzbuzz", FizzbuzzLive, :indexlive "/fizzbuzz", Fizzbuzz, :indexとすると、実行時に以下のようなエラーメッセージが出力されます。

== Compilation error in file lib/hello_web/router.ex ==
** (ArgumentError) could not infer :as option because a live action was given and the LiveView does not have a "Live" suffix. Please pass :as explicitly or make sure your LiveView is named like "FooLive" or "FooLive.Index"

ようは、サフィックスとして「Live」が必要ということのようです。

では、:indexを付与しない宣言live "/fizzbuzz", Fizzbuzzとした場合はどうなるか。

これは動きます。
その場合、テンプレートのファイルは「fizzbuzz.html.leex」となります。

たとえば、以下のように設定を変更してみます。

lib/hello_web/live/fizzbuzz.ex(抜粋)
defmodule HelloWeb.Fizzbuzz do
  use Phoenix.LiveView

  import Calendar.Strftime

  def mount(_params, _session, socket ) do
    if connected?(socket) do
      Process.send_after(self(), :tick, 1000)
      Process.send_after(self(), :fizzbuzz, 3000)
    end
    { :ok, assign( socket,
                   time: local_time(),
                   num: 0,
                   val: "initial" ) }
  end

〜(以下略)〜

end
lib/hello_web/live/fizzbuzz.html.leex
    <h2>これはfizzbuzz.html.leexの内容です</h2>
    <h2>こここここに現在時刻が表示されます。</h2>
    <span><%= @time %></span>
    <h2>ここにFizzBuzzの値が表示されます。</h2>
    <span><%= @num %>:<%= @val %></span>
lib/hello_web/router.ex(抜粋)
  scope "/", HelloWeb do
    pipe_through :browser

    live "/", PageLive, :index

#    live "/fizzbuzz", FizzbuzzLive, :index
    live "/fizzbuzz", Fizzbuzz
  end

そうすると、サフィックスにLiveがなくても、Collocating templatesとして動作します。

スクリーンショット 2020-05-11 20.08.15.png

とはいうものの、公式ドキュメントではLiveをつけた形式で紹介しているので、LiveViewとして動かすモジュールやファイル名にはLive_liveをつけておいた方が良いかもしれませんね。

6
1
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
6
1