Hacking Limbo

Reading / Coding / Hacking

Rails Nested Layout

一个 Rails 项目里经常会有多个不同的 layout,比如网站的前台显示、后台管理和登录注册页面各自使用不同的布局设计,而这些 layout 文件在代码级别又会存在一些共有的代码,像是 <head></head> 里的 meta tags,或者是 HTML5 Boilerplate 这类框架里用到的 conditional comments,如果每个文件都重复一遍这些代码的话,不仅不方便维护,代码看上去也没那么清晰。

按照 Rails 惯用的做法,可以把这些代码以 partial 的形式封装起来,然后在每个 layout 里面 render,算是一个比较简单的解决方案,适合 meta tags 这类可以直接插入的代码片段。但是对于 conditional comments 这种需要提供 nesting block 的,就不管用了,需要使用一些更“高级”的方法。

第一种方法是写成 helper method,利用 capture 方法将传给 helper 的 block 转换为字符串,把它跟需要插入的代码拼接起来(使用 string interpolation 或 content_tag)。比如这样:

# in helper
def insert_conditional_comments(&block)
  base = "<!--[if lt IE 7]> <html class='no-js lt-ie9 lt-ie8 lt-ie7' lang='zh-cn'> <![endif]--><!--[if IE 7]> <html class='no-js lt-ie9 lt-ie8 ie7' lang='zh-cn'> <![endif]--><!--[if IE 8]> <html class='no-js lt-ie9 ie8' lang='zh-cn'> <![endif]--><!--[if gt IE 8]><!--> %s <!--<![endif]-->"

  html = base % (capture { block.call })
  html
end
# in views
= insert_conditional_comments do
  %p some html or text here

另外一种方法跟 Django 框架的模板语法(extendblock)有点类似(当然内部实现应该是完全不同的),先调用 content_for 把 sub layout 的内容暂存起来,然后手动调用 render template: 'parent_layout',而 parent layout 中再通过 yield 把 sub layout 的内容插入到指定位置。比如这样:

# layouts/base.html.haml

!!!
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="zh-cn"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8 ie7" lang="zh-cn"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9 ie8" lang="zh-cn"> <![endif]-->
<!--[if gt IE 8]><!-->
%html.no-js{ :lang => 'zh-cn' }
  <!--<![endif]-->
  %head
    %meta{ :charset => 'utf-8' }
    %meta{ 'http-equiv' => 'X-UA-Compatible', :content => 'IE=edge,chrome=1' }
    = csrf_meta_tags

  %body
    = content_for?(:body) ? yield(:body) : yield
    = render 'shared/google_analytics'
# layouts/frontend.html.haml

- content_for :body do
  #wrapper
    #main= yield

= render :template => 'layouts/base'

注意上面的代码,在 base.html.haml 里我没有直接写 yield :body,而是先判断 content_for :body 有没有内容,如果没有的话就 yield——这样做的好处是 base.html.haml 自己也可以当成一个普通的 layout 来用。

通常情况下,两级的 nesting 就基本够用了,不过按照 Rails 官方文档的说法,nesting 是没有层级限制的,这一点跟 Django 也比较相似。(如果想在 Rails 里用 Django 的模板语法的话,可以试试 Liquid)。

参考:Layouts and Rendering in Rails