Yielding to content in Phoenix templates
The reason
So, there is something missing in Phoenix templating that I've had in other templating systems. Something where I can define a place where I can yield to content from a child template.
After some googling, I haven't found a solution that I like, although I think this one is starting on the right path. I'm gonna run with that idea, but change it up. I don't want to make a specific method for each piece of dynamic content I want to insert, and I certainly don't want one for each default either.
I'm going to walk you through my solution for this.
First make a helper
web/views/layout_helpers.ex
:
defmodule MyApp.LayoutHelpers do
import Phoenix.View, only: [render_existing: 3]
import Phoenix.Controller, only: [view_module: 1, action_name: 1]
def yield(name, assigns, default \\ "") do
assigns = Map.put(assigns, :action_name, action_name(assigns.conn))
render_existing(view_module(assigns.conn), name, assigns) || default
end
end
and then add it to the imports in web/web.ex
in the view
method:
def view do
quote do
use Phoenix.View, root: "web/templates"
# Import convenience functions from controllers
import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
# Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML
import MyApp.Router.Helpers
import MyApp.ErrorHelpers
import MyApp.Gettext
import MyApp.LayoutHelpers # Right here!
end
end
Using it
In a layout template file, you can now add it anywhere. An example of page-specific title tags, with a fallback.
<title><%= yield("title", assigns, "My App") %></title>
Then in a child view, you can add a render method that will catch it, like so:
defmodule MyApp.PostView do
use MyApp.Web, :view
def render("title", %{post: post}), do: post.title
end
And then the page will show the post's title.
If you want to do different things based on action_name
, you can do:
defmodule MyApp.PostView do
use MyApp.Web, :view
def render("title", assigns = %{action_name: action_name}) do
case action_name do
:show -> assigns[:post][:title]
:index -> "All the posts!"
end
end
end
That's basically all there is to it!
Simple, effective.