defmodule Phoenix.View do
@moduledoc """
Defines the view layer of a Phoenix application.
This module is used to define the application's main view, which
serves as the base for all other views and templates.
The view layer also contains conveniences for rendering templates,
including support for layouts and encoders per format.
## Examples
Phoenix defines the view template at `lib/your_app_web.ex`:
defmodule YourAppWeb do
# ...
def view do
quote do
use Phoenix.View, root: "lib/your_app_web/templates", namespace: "web"
# Import convenience functions from controllers
import Phoenix.Controller,
only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]
# Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML
import YourAppWeb.ErrorHelpers
import YourAppWeb.Gettext
# Alias the Helpers module as Routes
alias YourAppWeb.Router.Helpers, as: Routes
end
end
# ...
end
You can use the definition above to define any view in your application:
defmodule YourApp.UserView do
use YourAppWeb, :view
end
Because we have defined the template root to be "lib/your_app_web/templates", `Phoenix.View`
will automatically load all templates at "your_app_web/templates/user" and include them
in the `YourApp.UserView`. For example, imagine we have the template:
# your_app_web/templates/user/index.html.eex
Hello <%= @name %>
The `.eex` extension maps to a template engine which tells Phoenix how
to compile the code in the file into Elixir source code. After it is
compiled, the template can be rendered as:
Phoenix.View.render(YourApp.UserView, "index.html", name: "John Doe")
#=> {:safe, "Hello John Doe"}
## Rendering
The main responsibility of a view is to render a template.
A template has a name, which also contains a format. For example,
in the previous section we have rendered the "index.html" template:
Phoenix.View.render(YourApp.UserView, "index.html", name: "John Doe")
#=> {:safe, "Hello John Doe"}
When a view renders a template, the result returned is an inner
representation specific to the template format. In the example above,
we got: `{:safe, "Hello John Doe"}`. The safe tuple annotates that our
template is safe and that we don't need to escape its contents because
all data has already been encoded. Let's try to inject custom code:
Phoenix.View.render(YourApp.UserView, "index.html", name: "John
Doe")
#=> {:safe, "Hello John<br/>Doe"}
This inner representation allows us to render and compose templates easily.
For example, if you want to render JSON data, we could do so by adding a
"show.json" entry to `render/2` in our view:
defmodule YourApp.UserView do
use YourApp.View
def render("show.json", %{user: user}) do
%{name: user.name, address: user.address}
end
end
Notice that in order to render JSON data, we don't need to explicitly
return a JSON string! Instead, we just return data that is encodable to
JSON.
Both JSON and HTML formats will be encoded only when passing the data
to the controller via the `render_to_iodata/3` function. The
`render_to_iodata/3` function uses the notion of format encoders to convert a
particular format to its string/iodata representation.
Phoenix ships with some template engines and format encoders, which
can be further configured in the Phoenix application. You can read
more about format encoders in `Phoenix.Template` documentation.
"""
alias Phoenix.{Template}
@doc """
When used, defines the current module as a main view module.
## Options
* `:root` - the template root to find templates
* `:path` - the optional path to search for templates within the `:root`.
Defaults to the underscored view module name. A blank string may
be provided to use the `:root` path directly as the template lookup path
* `:namespace` - the namespace to consider when calculating view paths
* `:pattern` - the wildcard pattern to apply to the root
when finding templates. Default `"*"`
The `:root` option is required while the `:namespace` defaults to the
first nesting in the module name. For instance, both `MyApp.UserView`
and `MyApp.Admin.UserView` have namespace `MyApp`.
The `:namespace` and `:path` options are used to calculate template
lookup paths. For example, if you are in `MyApp.UserView` and the
namespace is `MyApp`, templates are expected at `Path.join(root, "user")`.
On the other hand, if the view is `MyApp.Admin.UserView`,
the path will be `Path.join(root, "admin/user")` and so on. For
explicit root path locations, the `:path` option can be provided instead.
The `:root` and `:path` are joined to form the final lookup path.
A blank string may be provided to use the `:root` path directly as the
template lookup path.
Setting the namespace to `MyApp.Admin` in the second example will force
the template to also be looked up at `Path.join(root, "user")`.
"""
defmacro __using__(opts) do
%{module: module} = __CALLER__
if Module.get_attribute(module, :view_resource) do
raise ArgumentError,
"use Phoenix.View is being called twice in the module #{module}. " <>
"Make sure to call it only once per module"
else
view_resource = String.to_atom(Phoenix.Template.resource_name(module, "View"))
Module.put_attribute(module, :view_resource, view_resource)
end
quote do
import Phoenix.View
use Phoenix.Template, Phoenix.View.__template_options__(__MODULE__, unquote(opts))
@before_compile Phoenix.View
@view_resource String.to_atom(Phoenix.Template.resource_name(__MODULE__, "View"))
@doc """
Renders the given template locally.
"""
def render(template, assigns \\ %{})
def render(module, template) when is_atom(module) do
Phoenix.View.render(module, template, %{})
end
def render(template, _assigns) when not is_binary(template) do
raise ArgumentError, "render/2 expects template to be a string, got: #{inspect template}"
end
def render(template, assigns) when not is_map(assigns) do
render(template, Enum.into(assigns, %{}))
end
@doc "The resource name, as an atom, for this view"
def __resource__, do: @view_resource
end
end
@doc false
defmacro __before_compile__(_env) do
# We are using @anno because we don't want warnings coming from
# render/2 to be reported in case the user has defined a catch-all
# render/2 clause.
quote generated: true do
# Catch-all clause for rendering.
def render(template, assigns) do
render_template(template, assigns)
end
end
end
@doc """
Renders the given layout passing the given `do/end` block
as `@inner_content`.
This can be useful to implement nested layouts. For example,
imagine you have an application layout like this:
# layout/app.html.eex