Layouts are special views, that render the “fixed” part of the HTML markup. This is the part that doesn’t change from page to page (perhaps navigation, sidebar, header, footer, etc.)

When we generate a new application, there is a default layout called Web::Views::ApplicationLayout with a apps/web/templates/application.html.erb template. It comes with a very basic HTML5 wireframe.

  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. <title>Web</title>
  5. </head>
  6. <body>
  7. <%= yield %>
  8. </body>
  9. </html>

The most interesting part is <%= yield %>. It’s replaced at the runtime with the output of a view. The order for rendering is first the view, and then the layout.

The context for a layout template is made of the layout and the current view. The latter has higher priority.

Imagine having the following line <title><%= page_title %></title>. If both the layout and the view implement page_title, Hanami will use the one from the view.

Configure Layout

The default layout is defined in an application’s configuration.

  1. # apps/web/application.rb
  2. module Web
  3. class Application < Hanami::Application
  4. configure do
  5. layout :application
  6. end
  7. end
  8. end

Hanami transforms the layout name in the application’s configuration, by appending the Layout suffix. For example, layout :application corresponds to Web::Views::ApplicationLayout.

If we want to disable a layout for a view, we can use a DSL for that:

  1. # apps/web/views/dashboard/index.rb
  2. module Web
  3. module Views
  4. module Dashboard
  5. class Index
  6. include Web::View
  7. layout false
  8. end
  9. end
  10. end
  11. end

If we want to turn off this feature entirely, we can set layout nil into the application’s configuration.

Using Multiple Template Layouts

Sometimes it’s useful to have more than one layout. For example, if the application.html.erb template contains navigation elements, and we want an entirely different layout, without navigation elements, for a login page, we can create a login.html.erb layout template.

Assuming we have a Web::Actions::UserSessions::New action to log a user in, we can create a login.html.erb template right next to the default application.html.erb in apps/web/templates/.

Then we need to create a new Web::Views::LoginLayout class, which will use the new layout template. This file can be named app/web/views/login_layout.rb(right next to the default application_layout.rb):

  1. module Web
  2. module Views
  3. class LoginLayout
  4. include Web::Layout
  5. end
  6. end
  7. end

Now, in our app/web/views/user_sessions/new.rb we can specify you’d like to use the login layout for this View:

  1. module Web
  2. module Views
  3. module UserSessions
  4. class New
  5. include Web::View
  6. layout :login
  7. end
  8. end
  9. end
  10. end

And we can add layout :login to any other View in this app that should use this same layout.

Optional Content

Sometimes it’s useful to render content only on certain pages. For example, this could be used to have page-specific javascript.

Given the following template for a layout:

  1. <!DOCTYPE HTML>
  2. <html>
  3. <!-- ... -->
  4. <body>
  5. <!-- ... -->
  6. <footer>
  7. <%= local :javascript %>
  8. </footer>
  9. </body>
  10. </html>

With following views:

  1. module Web
  2. module Views
  3. module Books
  4. class Index
  5. include Web::View
  6. end
  7. end
  8. end
  9. end

and

  1. module Web
  2. module Views
  3. module Books
  4. class Show
  5. include Web::View
  6. def javascript
  7. raw %(<script src="/path/to/script.js"></script>)
  8. end
  9. end
  10. end
  11. end
  12. end

The first view doesn’t respond to #javascript, so it safely ignores it. Our second object (Web::Views::Books::Show) responds to that method, so the result will be included in the final markup.