- module ClassMethods
- # If a layout is specified, all rendered actions will have their result rendered
- # when the layout <tt>yield</tt>s. 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, conditions = {}, auto = false)
- add_layout_conditions(conditions)
- write_inheritable_attribute "layout", template_name # 设置layout属性
- write_inheritable_attribute "auto_layout", auto # 设置不需要自动
- end
- def layout_conditions #:nodoc:
- @layout_conditions ||= read_inheritable_attribute("layout_conditions")
- end
- def default_layout(format) #:nodoc:
- layout = read_inheritable_attribute("layout") # 获取layout
- return layout unless read_inheritable_attribute("auto_layout") # 如果是当前controller设置的layout则返回layout
- @default_layout ||= {}
- @default_layout[format] ||= default_layout_with_format(format, layout)
- @default_layout[format]
- end
- def layout_list #:nodoc:
- Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] } # 获取所有layout页面
- end
- private
- # 继承layout的实现
- def inherited_with_layout(child)
- inherited_without_layout(child)
- unless child.name.blank?
- layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers///, '')
- # 如果子类没有layout,那么设置为自动layout
- child.layout(layout_match, {}, true) unless child.layout_list.grep(%r{layouts/#{layout_match}(/.[a-z][0-9a-z]*)+$}).empty?
- end
- end
- # 增加layout条件
- def add_layout_conditions(conditions)
- write_inheritable_hash "layout_conditions", normalize_conditions(conditions)
- end
- # 将conditions合并
- def normalize_conditions(conditions)
- conditions.inject({}) {|hash, (key, value)| hash.merge(key => [value].flatten.map {|action| action.to_s})}
- end
- # 根据格式来取得layout
- def default_layout_with_format(format, layout)
- list = layout_list
- if list.grep(%r{layouts/#{layout}/.#{format}(/.[a-z][0-9a-z]*)+$}).empty?
- (!list.grep(%r{layouts/#{layout}/.([a-z][0-9a-z]*)+$}).empty? && format == :html) ? layout : nil
- else
- layout
- end
- 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 <tt>layout "weblog/standard"</tt> will return
- # weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard.
- # 获取当前layout
- def active_layout(passed_layout = nil)
- layout = passed_layout || self.class.default_layout(response.template.template_format)
- active_layout = case layout
- when String then layout
- when Symbol then send!(layout)
- when Proc then layout.call(self)
- end
- # Explicitly passed layout names with slashes are looked up relative to the template root,
- # but auto-discovered layouts derived from a nested controller will contain a slash, though be relative
- # to the 'layouts' directory so we have to check the file system to infer which case the layout name came from.
- if active_layout
- if active_layout.include?('/') && ! layout_directory?(active_layout)
- active_layout
- else
- "layouts/#{active_layout}"
- end
- end
- end
- protected
- def render_with_a_layout(options = nil, extra_options = {}, &block) #:nodoc:
- template_with_options = options.is_a?(Hash)
- if (layout = pick_layout(template_with_options, options)) && apply_layout?(template_with_options, options) # 如果能找到并且能使用
- options = options.merge :layout => false if template_with_options
- logger.info("Rendering template within #{layout}") if logger
- content_for_layout = render_with_no_layout(options, extra_options, &block)
- erase_render_results
- add_variables_to_assigns
- @template.instance_variable_set("@content_for_layout", content_for_layout)
- response.layout = layout
- status = template_with_options ? options[:status] : nil
- render_for_text(@template.render_file(layout, true), status)
- else
- render_with_no_layout(options, extra_options, &block)
- end
- end
- private
- def apply_layout?(template_with_options, options)
- return false if options == :update
- template_with_options ? candidate_for_layout?(options) : !template_exempt_from_layout?
- end
- # 如果render有layout key 并且选项中没有:text, :xml, :json, :file, :inline, :partial, :nothing
- # 并且模板没有被从layout中免除
- def candidate_for_layout?(options)
- (options.has_key?(:layout) && options[:layout] != false) ||
- options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing).compact.empty? &
- !template_exempt_from_layout?(options[:template] || default_template_name(options[:action]))
- end
- #根据layout option获取layout
- def pick_layout(template_with_options, options)
- if template_with_options
- case layout = options[:layout]
- #如果layout=>false 那么返回nil
- when FalseClass
- nil
- # 如果是nil或者true并且拥有layout返回layout
- when NilClass, TrueClass
- active_layout if action_has_layout?
- else
- # 如果没有条件直接返回当前layout
- active_layout(layout)
- end
- else
- # 没有条件 则返回
- active_layout if action_has_layout?
- end
- end
- def action_has_layout?
- if conditions = self.class.layout_conditions
- case
- # 如果only包含=>true
- when only = conditions[:only]
- only.include?(action_name)
- # 如果except包含=>false
- when except = conditions[:except]
- !except.include?(action_name)
- else
- #默认true
- true
- end
- else
- # 没有条件true
- true
- end
- end
- def layout_directory?(layout_name)
- @template.finder.find_template_extension_from_handler(File.join('layouts', layout_name))
- end