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.
<!DOCTYPE HTML>
<html>
<head>
<title>Web</title>
</head>
<body>
<%= yield %>
</body>
</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.
# apps/web/application.rb
module Web
class Application < Hanami::Application
configure do
layout :application
end
end
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:
# apps/web/views/dashboard/index.rb
module Web
module Views
module Dashboard
class Index
include Web::View
layout false
end
end
end
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
):
module Web
module Views
class LoginLayout
include Web::Layout
end
end
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:
module Web
module Views
module UserSessions
class New
include Web::View
layout :login
end
end
end
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:
<!DOCTYPE HTML>
<html>
<!-- ... -->
<body>
<!-- ... -->
<footer>
<%= local :javascript %>
</footer>
</body>
</html>
With following views:
module Web
module Views
module Books
class Index
include Web::View
end
end
end
end
and
module Web
module Views
module Books
class Show
include Web::View
def javascript
raw %(<script src="/path/to/script.js"></script>)
end
end
end
end
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.