Over the last few weeks, we have been building a web application in one of our clients and ended up duplicating some template code. These new pages had something in common between them, but not with the rest of the application. We needed an inner layout to reuse the template code between these pages, however, Phoenix doesn’t come with this feature. In this post, you’ll learn how you can build nested layouts in Phoenix and when you should use them.
Why are they useful?
Inner (or nested) layouts are useful to reuse template code when you have subsections on your website. Usually, in Phoenix applications, we have /templates/layout/app.html.eex
that shares template code between all pages. For example:
In the example above, we want to reuse the application logo, title, and navigation menu across all pages. We can solve that by using the Phoenix default layout and it works great. However, sometimes you need to create a new website section inside the parent layout. For example, imagine we want to add a help section to our Phoenix website. Look at the image below:
We still want to reuse the layout header across all pages, but we also want to reuse the left navigation and main content layout in all pages inside the help section. Let’s see how we can do that using Phoenix.
The Inner Layout Solution
I tried some solutions by looking at some examples online. After discussing with the Plataformatec team, we came up with an approach that’s very simple and extensible, thanks to Phoenix explicit layout rendering. The solution works like this:
- You create the nested layout template.
- In the nested layout, you set the parent template by calling a function.
- The parent layout is aware that is possible to have an inner layout and will render it.
- You can invoke your nested layout by using the Phoenix `put_layout/2` function.
The main goal here is to make the nested layout work like any Phoenix layout. This will make it familiar to any Phoenix developer.
Preparing The Parent Template
First, let’s add a function that allows a template to render the parent layout:
# views/layout_view.ex
defmodule YourAppWeb.LayoutView do
use YourAppWeb, :view
def render_layout(layout, assigns, do: content) do
render(layout, Map.put(assigns, :inner_layout, content))
end
end
The render_layout/3
will render the given layout by assigning the contents of the inner layout given in the do
argument. Now, in the parent layout, we need to render the inner layout contents or the controller view contents. Look at how you can do it:
<%# templates/layout/app.html.eex %>
<%# ... %>
<%= Map.get(assigns, :inner_layout) || render @view_module, @view_template, assigns %>
<%# ... %>
With the code above, our parent layout is aware that there may be an inner layout and it should render its contents when available. That’s it! Now let’s see how we can use it.
Using the Nested Layout
You can create a nested layout template in the layouts
folder and use it like this:
<%# templates/layout/nested_layout.html.eex %>
<%= render_layout "app.html", assigns do %>
<%# Your HTML markup here %>
<%= render @view_module, @view_template, assigns %>
<%# More HTML markup here %>
<% end %>
We render the parent and put the inner content in the do/end
blocks in our nested layout template. Any content outside of the do/end
blocks will not be rendered. Don’t forget to call render @view_module, @view_template, assigns
, or the contents of your controller’s action template will not be rendered.
Now you can use our nested layout by invoking the plug put_layout/2
in your controller or router. For example:
defmodule YourAppWeb.NestedContentController do
use YourAppWeb, :controller
plug :put_layout, :nested_layout
def index(conn, params) do
# stuff
end
end
You can organize your nested layout files in a different way. For example, imagine you have a help section in your website and you want to keep the nested layout file in the help
folder. In your HelpView
you’ll need to import the render_layout/3
function, like this:
# views/help_view.ex
defmodule YourAppWeb.HelpView do
use YourAppWeb, :view
import YourAppWeb.LayoutView, only: [render_layout: 3]
end
After that, you can put your nested layout template in templates/help/layout.html.eex
. In the controller or router, you can invoke the nested layout like this:
plug :put_layout, {YourAppWeb.HelpView, "layout.html"}
The code above will render your layout.html.eex
template in views/help
directory using YourAppWeb.HelpView
module.
Wrapping Up
Inner layouts come in handy to reuse template code of subsections of your web application. You learned how simple it is to build that in Phoenix. Just be aware of not creating inner layouts of inner layouts. If you do that, your codebase will start to be very hard to maintain. You can see and try by yourself a sample Phoenix app running the inner layout example.
Have you implemented inner layout in a different way? Do you have any feature that you would like to see how we can build it in Phoenix? Let us know in your comments.