module ActionController #:nodoc: module Layout #:nodoc: def self.append_features(base) super base.class_eval do alias_method :render_without_layout, :render alias_method :render, :render_with_layout class << self alias_method :inherited_without_layout, :inherited end end base.extend(ClassMethods) end # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in # repeated setups. The inclusion pattern has pages that look like this: # # <%= render "shared/header" %> # Hello World # <%= render "shared/footer" %> # # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose # and if you ever want to change the structure of these two includes, you'll have to change all the templates. # # With layouts, you can flip it around and have the common structure know where to insert changing content. This means # that the header and footer is only mentioned in one place, like this: # # # <%= @content_for_layout %> # # # And then you have content pages that look like this: # # hello world # # Not a word about common structures. At rendering time, the content page is computed and then inserted in the layout, # like this: # # # hello world # # # == Accessing shared variables # # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with # references that won't materialize before rendering time: # #

<%= @page_title %>

# <%= @content_for_layout %> # # ...and content pages that fulfill these references _at_ rendering time: # # <% @page_title = "Welcome" %> # Off-world colonies offers you a chance to start a new life # # The result after rendering is: # #

Welcome

# Off-world colonies offers you a chance to start a new life # # == Inheritance for layouts # # Layouts are shared downwards in the inheritance hierarchy, but not upwards. Examples: # # class BankController < ActionController::Base # layout "layouts/bank_standard" # # class InformationController < BankController # # class VaultController < BankController # layout :access_level_layout # # class EmployeeController < BankController # layout nil # # The InformationController uses "layouts/bank_standard" inherited from the BankController, the VaultController overwrites # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all. # # == Types of layouts # # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can # be done either by specifying a method reference as a symbol or using an inline method (as a proc). # # The method reference is the preferred approach to variable layouts and is used like this: # # class WeblogController < ActionController::Base # layout :writers_and_readers # # def index # # fetching posts # end # # private # def writers_and_readers # logged_in? ? "writer_layout" : "reader_layout" # end # # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing # is logged in or not. # # If you want to use an inline method, such as a proc, do something like this: # # class WeblogController < ActionController::Base # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" } # # Of course, the most common way of specifying a layout is still just as a plain template path: # # class WeblogController < ActionController::Base # layout "layouts/weblog_standard" # # == Avoiding the use of a layout # # If you have a layout that by default is applied to all the actions of a controller, you still have the option to rendering # a given action without a layout. Just use the method render_without_layout, which works just like Base.render -- # it just doesn't apply any layouts. # # == Automatic layout assignment # # If there is a template in app/views/layouts/ with the same name as the current controller then it will be automatically # set as that controller's layout unless explicitly told otherwise. Say you have a WeblogController, for example. If a template named # app/views/layouts/weblog.rhtml or app/views/layouts/weblog.rxml exists then it will be automatically set as # the layout for your WeblogController. You can create a layout with the name application.rhtml or application.rxml # and this will be set as the default controller if there is no layout with the same name as the current controller and there is # no layout explicitly assigned with the +layout+ method. Setting a layout explicity will always override the automatic behaviour. module ClassMethods # If a layout is specified, all actions rendered through render and render_action will have their result assigned # to @content_for_layout, which can then be used by the layout to insert their contents with # <%= @content_for_layout %>. This layout can itself depend on instance variables assigned during action # performance and have access to them as any normal template would. def layout(template_name) write_inheritable_attribute "layout", template_name end private def inherited(child) inherited_without_layout(child) child.layout(child.controller_name) unless layout_list.grep(/^#{child.controller_name}\.r(?:xml|html)$/).empty? end def layout_list Dir.glob("#{template_root}/layouts/*.r{xml,html}").map { |layout| File.basename(layout) } end end # Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method # is called and the return value is used. Likewise if the layout was specified as an inline method (through a proc or method # object). If the layout was defined without a directory, layouts is assumed. So layout "weblog/standard" will return # weblog/standard, but layout "standard" will return layouts/standard. def active_layout(passed_layout = nil) layout = passed_layout || self.class.read_inheritable_attribute("layout") active_layout = case layout when Symbol then send(layout) when Proc then layout.call(self) when String then layout end active_layout.include?("/") ? active_layout : "layouts/#{active_layout}" if active_layout end def render_with_layout(template_name = default_template_name, status = nil, layout = nil) #:nodoc: if layout ||= active_layout add_variables_to_assigns logger.info("Rendering #{template_name} within #{layout}") unless logger.nil? @content_for_layout = @template.render_file(template_name, true) render_without_layout(layout, status) else render_without_layout(template_name, status) end end end end