Layouts

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.

Note

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.

Splitting View from Layout

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.

Applying a layout to a view

Figure 3-3. Applying a layout to a view

Creating a Default Layout

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.

Deciding which layout to use, based on naming conventions

Figure 3-4. Deciding which layout to use, based on naming conventions

Choosing a Layout from a Controller

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 < ApplicationController

  layout :adminOrUser

  def index
    ...
  end

private
      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

Note

You can also select a layout (or no layout) using the render function. (You may want to do this if your controller includes multiple actions that need their own layouts.)

Sharing Template Data with the Layout

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.

Layout including content created as a separate piece by a template

Figure 3-5. Layout including content created as a separate piece by a template

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset