Tutorial
Nested Templates and Layouts in Phoenix Framework
Eventually when your site reaches a certain maturity, you look into ways to refactor the code. One is to add parts of the templates in reusable layout templates. The reason is that HTML templates can be pretty verbose and you want certain elements to look the same across the site.
This approach is an alternative to using partials and even though partials are fine, it might be a little cumbersome if you want to pass in a lot of content. Like a form or a large block of HTML.
STEP 1 - Create a shared components module
All the shared components will live in a module called SharedComponents
. I usually add the view-related helpers in the views folder.
# lib/sample_web/views/shared_components.ex
defmodule SampleWeb.SharedComponents do
def card(assigns \\ %{}, do: block), do: render_template("card.html", assigns, block)
defp render_template(template, assigns, block) do
assigns =
assigns
|> Map.new()
|> Map.put(:inner_content, block)
SampleWeb.SharedView.render(template, assigns)
end
end
But I also want to add an actual view file for rendering the templates:
# lib/sample_web/views/shared_view.ex
defmodule SampleWeb.SharedView do
use SampleWeb, :view
end
STEP 2 - Add HTML templates
I will start by adding the card component. Note that since I am using it as a block, everything inside the block will be outputted with the <%= @inner_content %>
<!-- /templates/shared/card.html.eex -->
<div class="max-w-sm rounded overflow-hidden shadow-lg">
<div class="px-6 py-4">
<%= @inner_content %>
</div>
</div>
Moving on to the card header. Note that I also made it so I can pass in arguments when I call the <%= card_header do %>
function. In this case, I might want some extra HTML class attributes.
<!-- /templates/shared/card_header.html.eex -->
<div class="bg-gray-50 py-4 px-6 border-b border-gray-200 rounded-t-lg <%= assigns[:class] %>">
<%= @inner_content %>
</div>
And the same thing goes for the card body component.
<!-- /templates/shared/card_body.html.eex -->
<div class="px-8 py-8 <%= assigns[:class] %>">
<%= @inner_content %>
</div>
STEP 3 - Start using the card component
With the templates set up, I should now be able to start using the cards in different templates. I can use only the outer card like this:
<%= card do %>
<div class="font-bold text-xl mb-2">The Coldest Sunset</div>
<p class="text-gray-700 text-base">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatibus quia, nulla! Maiores et perferendis eaque, exercitationem praesentium nihil.
</p>
<% end %>
Or I can nest the components with cardheader
and cardbody
inside the card
:
<%= card do %>
<%= card_header do %>
The Coldest Sunset
<% end %>
<%= card_body do %>
<p class="text-gray-700 text-base">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatibus quia,
nulla! Maiores et perferendis eaque, exercitationem praesentium nihil.
</p>
<% end %>
<% end %>