Back in Example 2-1,
you saw that the view Rails originally generated only included an
h1
element and a p
element—Rails didn’t provide a full HTML
document, with a DOCTYPE
, html
element, or head
and body
elements. Part of the reason for that is
that Rails expects its views to work as part of a system, a system in
which another document, a layout, provides all of that supporting
infrastructure. Rails didn’t automatically generate a layout, but
creating one is easy.
When Rails generates more complete scaffolding code (described in Chapter 5), it does produce a layout file. It just doesn’t normally do it when generating a controller and view.
The final version of the Hello view, still using the simple controller from Example 2-4, looks like Example 3-3.
Example 3-3. The Hello view, containing markup that can move to a layout
<html> <head><title><%=h @message %> </title> <%= stylesheet_link_tag 'hello', :media => "all", :type => "text/css", :href => "/stylesheets/" %> </head> <body> <h1><%=h @message %></h1> <p>This is a greeting from app/views/hello/index.html.erb</p> <% for i in 1..@count %> <p><%=h @bonus %></p> <% end %> </body> </html>
To make this into a layout, break the view down into two files. The first, listed in Example 3-4, contains the logic specific to presenting that page, while the second, Example 3-5, contains the broader framing for the document. (Both are included in ch03/hello005.)
Example 3-4. The Hello view, reduced to its local logic
<h1><%= @message %></h1> <p>This is a greeting from app/views/hello/index.html.erb</p> <% for i in 1..@count %> <p><%= @bonus %></p> <% end %>
Example 3-5. A layout for the Hello view, in app/views/layouts/hello.html.erb
<html> <head><title><%=h @message %> </title> <%= stylesheet_link_tag 'hello', :media => "all", :type => "text/css", :href => "/stylesheets/hello.css" %> </head> <body>(using layout)
<!--layout will incorporate view--><%= yield :layout %>
</body> </html>
The (using layout)
text just
gives us a visible marker to see that content is coming from the
layout. (It’ll go away immediately after this example.) The <%= yield :layout%>
tag tells Rails to shift its processing to the content
that goes inside of the layout. (You can also use the simpler <%= yield %>
.) When Rails encounters
this, it will work on the Hello view, bring its content into the page,
and then finish the layout, sending the combined results to the
requesting browser.
For this to work, however, Rails needs to know where to find the layout. To work by default with the Hello controller and view, it should go in app/views/layouts/ as hello.html.erb. Example 3-4 should replace the old app/views/hello/index.rhtml. When opened in the browser, the layout and view will combine to produce the HTML shown in Example 3-6 and Figure 3-3.
Example 3-6. Combining a layout and a view produces a complete result
<html>
<head><title>Hello! </title>
<link href="/stylesheets/hello.css" media="all" rel="stylesheet" type="text/css"
/>
</head>
<body>
(using layout)
<!--layout will incorporate view-->
<h1>Hello!</h1>
<p>This is a greeting from app/views/hello/index.rhtml</p>
<p>This message came from the controller.</p>
<p>This message came from the controller.</p>
<p>This message came from the controller.</p>
</body>
</html>
There’s another piece here worth noting, highlighted in Example 3-6. The title
element contains the same content—coming from the
@message
variable—as the original
view did. The layout has access to all of the same variables as the
view. If you were creating a layout that was going to be used for many
different controllers, you might want to choose a more specific
variable name for that piece, say @page_title
, and make certain that all of
your controllers support it.
A lot of sites use the same general structure—headers, stylesheets, and often navigation—across many or all pages. While you certainly could create a copy of the layout file for every controller your application uses, that would violate a core principle of Rails: Don’t Repeat Yourself, or DRY. Much of the time, it’ll make much more sense to create a layout that acts as the default for your entire application, and only create different layouts for the cases where you actually need them.
Creating a default layout for your entire application is simple—just create a layout named application.html.erb in the app/views/layouts folder. So long as Rails doesn’t find a layout specific to your controller—and you don’t tell it to do something different—Rails will use application.html.erb throughout your project, making it easy to create a consistent overall look. (This approach is demonstrated in ch03/hello006.) The naming conventions Rails follows to decide on a layout are shown in Figure 3-4.
Left to its own devices, Rails assumes that each view has a layout file associated with it by the naming convention, or uses the default for the application. There are many cases, though, where groups of related views share a common layout, but that layout isn’t necessarily the application default. It’s much easier to manage that common layout from a single file rather than having to change a layout for every controller every time the design changes.
The simplest way to make this work is to have controllers
specify what layout they would like to use. If standardization is your
main purpose, adding a layout
declaration like that shown in Example 3-7 will
work.
Example 3-7. Specifying a layout choice in a controller
class HelloController < ApplicationController
layout "standardLayout"
def index
@message="Hello!"
@count=3
@bonus="This message came from the controller."
end
end
Instead of looking for app/views/layouts/hello.html.erb to be the layout, Rails will now look for app/views/layouts/standardLayout.html.erb.
The layout
call can also take
nil
(for no layout) or a symbol as
a method reference. If there is a method reference, that method will
determine which layout is used. Example 3-8 shows what this
might look like.
Example 3-8. Choosing a layout based on program calculations
class HelloController < ApplicationControllerlayout :adminOrUser
def index ... endprivate
def adminOrUser
if adminAuthenticated
"admin_screen"
else
"user_screen"
end
end
end
In this case, layout
took a
reference to the adminOrUser
method, which returned either the admin_screen
layout or the user_screen
layout as its choice depending
on the value of the adminAuthenticated
variable (whose value is
calculated somewhere else).
One other feature of layout
is worth noting, though we’re not ready to use it yet. If your
application can return, say, XML or RSS instead of HTML, you may want
to be able to turn off your HTML layout in cases where it won’t be
wanted. You might say:
layout "standardLayout", :except => :rss layout "standardLayout", :except => [:rss, :xml, :text_only]
The first one uses the layout except when RSS has been requested, while the second uses the layout except for requests for RSS, XML, and text formats. You could also work the opposite way, saying to use the layout only for HTML:
layout "standardLayout", :only => :html
Layouts and view templates share the same information from the controller, but there may be times when a view template should include information that needs to be embedded in the layout. This might be navigation particular to different areas of a site, or personalization, or some kind of status bar, for instance, that shows the user how far they’ve gone through a particular task.
Example 3-9
shows a modified template that creates a numbered list HTML fragment
that the layout in Example 3-10 will include
separately—actually, before—it includes the main
template output. The structure created by the <% content_for(:name) do %>
code in
Example 3-9 is called
upon by the <%= yield :name
%>
tag in Example 3-10.
Example 3-9. index.html.erb with newly added HTML structure for separate inclusion
<h1><%=h @message %></h1> <p>This is a greeting from app/views/hello/index.html.erb</p> <% for i in 1..@count %> <p><%=h @bonus %></p> <% end %><% content_for(:list) do %>
<ol>
<% for i in 1..@count %>
<li><%=h @bonus %></li>
<% end %>
</ol>
<% end %>
Example 3-10. Template with added yield, exposing the list from Example 3-9
<html>
<head><title><%=h @message %> </title>
<%= stylesheet_link_tag 'hello', :media => "all",
:type => "text/css", :href => "/stylesheets/hello.css" %>
</head>
<body>
<%= yield :list %>
<!--layout will incorporate view-->
<%= yield :layout %>
</body>
</html>
The result, shown in Figure 3-5, isn’t exactly beautiful, but it demonstrates that a template can create content that a layout can include anywhere it likes.
Always remember that this works because the template has executed before the layout adds its own ideas. You can communicate from the template to the layout, but not from the layout to the template.