技術メモ

プログラミングとか電子工作とか

Phoenix入門 (第8章 Views その1)

f:id:ysmn_deus:20190219130922p:plain

どうも、靖宗です。 今回はViewsということで、実際にレンダリング処理など一番クライアント側に近い処理の話でしょうか。
とりあえず進めて行きます。

PhoenixのViewの役割はなんと言ってもレンダリングです。HTMLをレンダリングすることもあれば、JSONレンダリングすることもあるでしょう。
基本的にレンダリングはレイアウトを含むテンプレートをレンダリングすることを意味します。(Controllersの章でもJSONXMLのテンプレートも出てきました。)

Rendering Templates

Controllersの章でも書きましたが、Phoenixは結構ガチガチな命名規則で成り立っています。
PageControllerというコントローラはPageViewが必要で、PageViewではlib/hello_phoenix_web/templates/pageというディレクトリにあるテンプレートをレンダリングします。
このディレクトリはlib/hello_phoenix_web.exview/0関数の中で変更可能ですが、あまり変更することは無いと思います。

...
  def view do
    quote do
      use Phoenix.View,
        root: "lib/hello_phoenix_web/templates", # ←これ
        namespace: HelloPhoenixWeb
...

Phoenixはプロジェクトを開始するとlib/hello_phoenix_web/views下にErrorViewLayoutViewPageViewという3つのビューを自動作成してくれます。
おそらくこれらが基本的なビューを構成する最小要素になるんでしょう。まずはLayoutViewを見てみます。

defmodule HelloPhoenixWeb.LayoutView do
  use HelloPhoenixWeb, :view
end

基本的にビューを利用する時の最小構成はこれだけでいいようです。
ただしわざわざ静的ページを生成するためにPhoenixを採用する人は少ないはずです。折角なのでレイアウトのテンプレートをいじってみます。
lib/hello_phoenix_web/templates/layout/app.html.eexを編集します。

<!- <title>Hello · Phoenix Framework</title> ->
<title><%= title() %></title>

テンプレート内で関数を呼び出してタイトルを代入します。LayoutViewを編集します。

defmodule HelloPhoenixWeb.LayoutView do
  use HelloPhoenixWeb, :view

  def title do
    "Awesome New Title!"
  end
end

タイトルが変わりました。

f:id:ysmn_deus:20190309182210p:plain

<%=%>でかこうお作法はElixirのEExというプロジェクト由来だそうです。

このテンプレートはコンパイル時に*.html.eexをビューモジュールの中に取り込んで実行時にはメモリ上に展開されているようです。
title/0をどこかで実行せずに、テンプレート内にtitle()と書けるのはこの辺の恩恵のようです。

また、Viewでuse HelloWeb, :viewをしているときはHelloPhoenixWeb.Router.HelpersRoutesとしてエイリアスされているようです。
なので、ビューやテンプレート上ではPath helperがRoutes.*_pathとして使えます。
なので、リンクを張るときは

<a href="<%= Routes.page_path(@conn, :index) %>">Link back to this page</a></p>

のように記載できます。
ただし、個人的にはビューは切り分けて別のフロントフレームワークで管理するべきでは?とも思うので、この辺は軽く流します。

More About Views

ビューの挙動についてもう少しだけ深掘りします。
lib/hello_phoenix_web/templates/page/test.html.eexというファイルを下記のように作ります。

This is the message: <%= message() %>

あとlib/hello_phoenix_web/views/page_view.exも変更します。

defmodule HelloPhoenixWeb.PageView do
  use HelloPhoenixWeb, :view

  def message do
    "Hello from the view!"
  end
end

本当はルーティングの設定も書いてやらないとブラウザでは表示されませんが、iex上で挙動を確認してみます。
iex -S mixでアプリケーションを起動して、render関数を直接叩いてやります。

iex(1)> Phoenix.View.render(HelloPhoenixWeb.PageView, "test.html", %{})
{:safe, ["This is the message: ", "Hello from the view!"]}

なにやらタプルが返ってきてます。
ここで:safeはHTMLエスケープされた情報であることを意味しているようです。(&ltとかになっちゃう奴)
なにやらPhoenix.HTML.Safeプロトコルというものの仕様だそうですが、話が長くなりそうなので割愛します。

render/3関数で第三引数に空のマップを渡してますが、これはテンプレートに渡す変数です。
試しにlib/hello_phoenix_web/templates/page/test.html.eexを編集して確認してみます。

I came from assigns: <%= @message %>
This is the message: <%= message() %>

リロードして変数を渡してみます。

iex(2)> r HelloPhoenixWeb.PageView
warning: redefining module HelloPhoenixWeb.PageView (current version loaded from _build/dev/lib/hello_phoenix/ebin/Elixir.HelloPhoenixWeb.PageView.beam)
  lib/hello_phoenix_web/views/page_view.ex:1

{:reloaded, HelloPhoenixWeb.PageView, [HelloPhoenixWeb.PageView]}
iex(3)> Phoenix.View.render(HelloPhoenixWeb.PageView, "test.html", message: "Assigns has an @.")
{:safe,
 ["I came from assigns: ", "Assigns has an @.", "\r\nThis is the message: ",
  "Hello from the view!"]}

なにやらPhoenixのドキュメントのサンプルと表示は異なってますが、意味は同じ筈です。(ドキュメントはリストがヘッダと後続リスト部で別れた表記になってる)
どうやらHTMLデータはリストで提供され、関数や変数のある箇所で区切られて処理され、最終的にリストとして渡されるという仕組みになっているようです。
あと、折角エスケープの話が出てきたのでエスケープされるような文字列も試します。

iex(4)> Phoenix.View.render(HelloPhoenixWeb.PageView, "test.html", message: "<script>badThings();</script>")
{:safe,
 [
   "I came from assigns: ",
   [
     [[[[] | "&lt;"], "script" | "&gt;"], "badThings();" | "&lt;"],
     "/script" |
     "&gt;"
   ],
   "\r\nThis is the message: ",
   "Hello from the view!"
 ]}

めちゃくちゃになっとるやん・・・
やはり何か処理が挟まる度にリストで区切られるようです。

レンダリングされた文字列(リスト)のみ必要な場合(:safeとかいらない場合)はrender_to_iodata/3という関数があるようです。

iex(5)> Phoenix.View.render_to_iodata(HelloPhoenixWeb.PageView, "test.html", message: "Assigns has an @.")
["I came from assigns: ", "Assigns has an @.", "\r\nThis is the message: ",
 "Hello from the view!"]

A Word About Layouts

レイアウトも普通のテンプレートと同じ事はControllersの章で学びました。
じゃあどうやって各々のビューでこのレイアウトを取り込んでるんでしょうか?
lib/hello_phoenix_web/templates/layout/app.html.eexを見てみます。

...
<%= render @view_module, @view_template, assigns %>
...

この箇所で他のビューを挿入しています。カスタマイズするときはこの場所を変えたりするんでしょうか。

今回も少々長くなってるので区切ります。
多分次の記事でビューは終わるはず