どうも、靖宗です。
今回はViewsということで、実際にレンダリング処理など一番クライアント側に近い処理の話でしょうか。
とりあえず進めて行きます。
PhoenixのViewの役割はなんと言ってもレンダリングです。HTMLをレンダリングすることもあれば、JSONをレンダリングすることもあるでしょう。
基本的にレンダリングはレイアウトを含むテンプレートをレンダリングすることを意味します。(Controllersの章でもJSONやXMLのテンプレートも出てきました。)
Rendering Templates
Controllersの章でも書きましたが、Phoenixは結構ガチガチな命名規則で成り立っています。
PageController
というコントローラはPageView
が必要で、PageView
ではlib/hello_phoenix_web/templates/page
というディレクトリにあるテンプレートをレンダリングします。
このディレクトリはlib/hello_phoenix_web.ex
のview/0
関数の中で変更可能ですが、あまり変更することは無いと思います。
... def view do quote do use Phoenix.View, root: "lib/hello_phoenix_web/templates", # ←これ namespace: HelloPhoenixWeb ...
Phoenixはプロジェクトを開始するとlib/hello_phoenix_web/views
下にErrorView
、LayoutView
、PageView
という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
タイトルが変わりました。
<%=
と%>
でかこうお作法はElixirのEExというプロジェクト由来だそうです。
このテンプレートはコンパイル時に*.html.eex
をビューモジュールの中に取り込んで実行時にはメモリ上に展開されているようです。
title/0
をどこかで実行せずに、テンプレート内にtitle()
と書けるのはこの辺の恩恵のようです。
また、Viewでuse HelloWeb, :view
をしているときはHelloPhoenixWeb.Router.Helpers
がRoutes
としてエイリアスされているようです。
なので、ビューやテンプレート上では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エスケープされた情報であることを意味しているようです。(<
とかになっちゃう奴)
なにやら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: ", [ [[[[] | "<"], "script" | ">"], "badThings();" | "<"], "/script" | ">" ], "\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 %> ...
この箇所で他のビューを挿入しています。カスタマイズするときはこの場所を変えたりするんでしょうか。
今回も少々長くなってるので区切ります。
多分次の記事でビューは終わるはず