Subscribe to access all episodes. View plans →

#94: Multiple Layouts with Phoenix

Published May 27, 2019

Elixir 1.8

Phoenix 1.4


Here we have an application that lists some albums. It also has a blog that we can see is using the same layout as the other pages. In this episode let’s see how we can render different layouts for different pages of our application, specifically our blog.

The first thing we’ll want to do is create a new file in our templates/layout directory named blog.html.eex. I’ll paste in the layout that we want to use for our blog.

Template path: lib/teacher_web/templates/layout/blog.html.eex

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>Blog</title>
    <link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
  </head>
  <body class="blog">
    <main role="main" class="container">
      <%= link "Albums", to: Routes.album_path(@conn, :index) %>
      | <%= link "Blog", to: Routes.post_path(@conn, :index) %>
      <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
      <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
      <%= render @view_module, @view_template, assigns %>
    </main>
    <script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
  </body>
</html>

Now that we have our new layout, we need to tell our application to use it when we render our blog page.

Let’s open our post_controller.ex and one way we can specify the layout to use is in the render function. We see we can use the layout option with two element tuple that has our layout view module and our template.

So let’s go to our index action and we can pass in our layout.

lib/teacher_web/controllers/post_controller.ex

...

def index(conn, _params) do
  posts = Blog.list_posts()
  render(conn, "index.html",
    posts: posts,
    layout: {TeacherWeb.LayoutView, "blog.html"})
end

...

Now let’s go back to our browser and if we go to our “blog” page. Great - we see our blog specific layout with a new background color and font style for our blog’s title. Since we are only rendering our blog layout for the “index” action, if we click into a specific post, we see our blog template is no longer rendered.

We’ll go back to our controller and update it so that all of our actions rendered for our blog controller use the blog template. To do that, we can use the Phoenix.Controller.put_layout function.

Let’s go ahead and remove the template from our render function. Then we’ll add plug :put_layout with the name of our layout template - “blog.html”.

lib/teacher_web/controllers/post_controller.ex

...
plug :put_layout, "blog.html"
...

Then if we go back to the blog post and refresh the page we see our new layout is being used. Now this works great for layouts that are specific to a single controller. But if we needed to use this same layout template for multiple controllers, we would have to add it to each controller. Another approach would be to create a new pipeline in our router and then use it for all the controllers we want to use that layout for. Let’s try this.

We’ll go back to our post_controller.ex and remove our put_layout and then let’s open our router and create a new pipeline that we’ll call blog. Then inside of the pipeline we’ll add plug :put_layout and here we’ll need to include our view as the first element in a two element tuple with our template.

Let’s create a new scope for our controllers that we want to use the blog layout. And we’ll include pipe_through with the browser and blog and let’s include our posts resource and our albums.

lib/teacher_web/router.ex

...

pipeline :blog do
  plug :put_layout, {TeacherWeb.LayoutView, "blog.html"}
end

scope "/", TeacherWeb do
  pipe_through [:browser]

  resources "/sessions", SessionController, only: [:new, :create]
  delete "/sign-out", SessionController, :delete
  resources "/registrations", RegistrationController, only: [:new, :create]
  # resources "/albums", AlbumController
  # resources "/posts", PostController

  get "/", PageController, :index

end

scope "/", TeacherWeb do
  pipe_through [:browser, :blog]

  resources "/albums", AlbumController
  resources "/posts", PostController

end

...

Then if we go back to the browser we can see that both our albums and our blogs are using our new layout.

© 2024 HEXMONSTER LLC