5.1 添加一些结构
本书介绍 Web 开发而不是 Web 设计,不过在一个看起来很简陋的应用中开发会让人提不起劲,所以本节要向布局中添加一些结构,再加入一些 CSS 实现基本的样式。除了使用自定义的 CSS 之外,我们还会使用由 Twitter 开发的开源 Web 设计框架 Bootstrap。我们会按照一定的方式组织代码——当布局文件中的内容变多以后,使用局部视图清理。
开发 Web 应用时,尽早对用户界面有个统筹安排往往会对你有所帮助。在本书后续内容中,我会经常使用网页构思图(mockup)(在 Web 领域经常称之为“线框图”),展示应用最终外观的草图。[2]本章大部分内容都在开发 3.2 节编写的静态页面,我们要在页面中加入一个网站 LOGO、导航条和网站底部。这些页面中最重要的是“首页”,它的构思图如图 5.1 所示,图 5.7 是最终实现的效果。你会发现二者之间的某些细节有所不同,例如,在最终实现的页面中我们加入了一个 Rails LOGO——这没什么关系,因为构思图没必要画出每个细节。
图 5.1:演示应用首页的构思图
和之前一样,如果使用 Git 做版本控制,现在最好创建一个新分支:
$ git checkout master
$ git checkout -b filling-in-layout
5.1.1 网站导航
在应用中添加链接和样式之前,我们先来修改网站的布局文件 application.html.erb
(上一次见到是在代码清单 4.3 中),添加一些 HTML 结构。我们要添加一些区域,一些 CSS 类,以及导航条。布局文件的完整内容参见代码清单 5.1, 对各部分的说明紧跟其后。如果你迫不及待想看到结果,请看图 5.2。(注意:结果(还)不是很让人满意。)
代码清单 5.1:添加一些结构后的网站布局文件
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
</script>
<![endif]-->
</head>
<body>
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<%= link_to "sample app", '#', id: "logo" %>
<nav>
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Home", '#' %></li>
<li><%= link_to "Help", '#' %></li>
<li><%= link_to "Log in", '#' %></li>
</ul>
</nav>
</div>
</header>
<div class="container">
<%= yield %>
</div>
</body>
</html>
我们从上往下看一下这段代码中新添加的元素。3.4.1 节简单介绍过,Rails 默认使用 HTML5(如 <!DOCTYPE html>
所示)。因为 HTML5 标准还很新,有些浏览器(特别是旧版 IE 浏览器)还没有完全支持,所以我们加载了一些 JavaScript 代码(称作“HTML5 shim”)来解决这个问题:
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
</script>
<![endif]-->
如下有点古怪的句法
<!--[if lt IE 9]>
只有当 IE 浏览器的版本号小于 9 时(if lt IE 9
)才会加载其中的代码。这个奇怪的 [if lt IE 9]
句法不是 Rails 提供的,其实它是 IE 浏览器为了解决兼容性问题而特别提供的条件注释。使用这个句法有个好处,只会在 IE9 以前的版本中加载 HTML5 shim,而 Firefox、Chrome 和 Safari 等其他浏览器不会受到影响。
后面的区域是一个 header
,包含网站的 LOGO(纯文本)、一些区域(使用 div
标签)和一个导航列表元素:
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<%= link_to "sample app", '#', id: "logo" %>
<nav>
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Home", '#' %></li>
<li><%= link_to "Help", '#' %></li>
<li><%= link_to "Log in", '#' %></li>
</ul>
</nav>
</div>
</header>
header
标签表明这个元素应该放在页面的顶部。我们为 header
标签指定了三个 CSS 类,[3]分别为 navbar
、navbar-fixed-top
和 navbar-inverse
,类之间用空格分开:
<header class="navbar navbar-fixed-top navbar-inverse">
所有 HTML 元素都可以指定类和 ID,它们不仅是标记,使用 CSS 编写样式时也有用(5.1.2 节)。类和 ID 之间主要的区别是,类可以在同一个网页中多次使用,而 ID 只能使用一次。这里的三个类在 Bootstrap 框架中都有特殊的意义。我们会在 5.1.2 节安装并使用 Bootstrap。
在 header
标签内部,有一个 div
标签:
<div class="container">
div
标签是常规的区域,除了把文档分成不同的部分之外,没有特殊的意义。在以前的 HTML 标准中,div
标签被用来划分网站中几乎所有的区域,但是 HTML5 增加了 header
、nav
和 section
等元素,用来划分大多数网站中都会用到的区域。这个 div
标签也有一个 CSS 类,container
。和 header
标签的类一样,这个类在 Bootstrap 中也有特殊的意义。
在这个 div
标签中有一些 ERb 代码:
<%= link_to "sample app", '#', id: "logo" %>
<nav>
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Home", '#' %></li>
<li><%= link_to "Help", '#' %></li>
<li><%= link_to "Log in", '#' %></li>
</ul>
</nav>
这里使用 Rails 提供的 link_to
辅助方法创建链接(3.2.2 节直接使用 a
标签创建)。link_to
的第一个参数是链接文本,第二个参数是链接地址。在 5.3 节我们会使用“具名路由”(named route)指定链接地址,现在暂且使用 Web 开发中经常使用的占位符 #
。第三个参数可选,是一个哈希,本例使用这个参数为 LOGO 添加一个 CSS ID——logo
。(其他三个链接没有使用这个哈希参数,没关系,因为这个参数是可选的。)Rails 辅助方法的参数经常这样使用哈希,让我们仅使用 Rails 的辅助方法就能灵活添加 HTML 属性。
div
标签中的第二个元素是导航链接,使用无序列表标签 ul
,以及列表项目标签 li
编写:
<nav>
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Home", '#' %></li>
<li><%= link_to "Help", '#' %></li>
<li><%= link_to "Log in", '#' %></li>
</ul>
</nav>
nav
标签以前是不需要的,它的目的是明确表明这些链接是导航。ul
标签中的 nav
、navbar-nav
和 navbar-right
三个类在 Bootstrap 中有特殊的意义,5.1.2 节引入 Bootstrap 的 CSS 之后会自动实现特殊的样式。在浏览器中审查导航元素,你会发现 Rails 处理布局文件并执行其中的 ERb 代码后,生成的列表如下所示:[4]
<nav>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">Home</a></li>
<li><a href="#">Help</a></li>
<li><a href="#">Log in</a></li>
</ul>
</nav>
这就是返回给浏览器的文本。
布局文件的最后一部分是一个 div
标签,用于显示主内容:
<div class="container">
<%= yield %>
</div>
和之前一样,container
类在 Bootstrap 中有特殊意义。3.4.3 节已经介绍过,yield
会把各页面中的内容插入网站的布局中。
除了网站的底部(5.1.3 节会添加)之外,布局现在完成了。访问“首页”就能看到结果。为了利用后面添加的样式,我们要在 home.html.erb
视图中添加一些额外元素,如代码清单 5.2 所示。
代码清单 5.2:“首页”视图,包含一个到注册页面的链接
app/views/static_pages/home.html.erb
<div class="center jumbotron">
<h1>Welcome to the Sample App</h1>
<h2>
This is the home page for the
<a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</h2>
<%= link_to "Sign up now!", '#', class: "btn btn-lg btn-primary" %>
</div>
<%= link_to image_tag("rails.png", alt: "Rails logo"),
'http://rubyonrails.org/' %>
其中第一个 link_to
创建一个占位链接,指向第 7 章创建的用户注册页面:
<a href="#" class="btn btn-lg btn-primary">Sign up now!</a>
div
标签的 CSS 类 jumbotron
在 Bootstrap 中有特殊的意义,注册按钮的 btn
、btn-lg
和 btn-primary
也是一样。
第二个 link_to
用到了 image_tag
辅助方法,第一个参数是图片的路径;第二个参数可选,是一个哈希,本例中这个哈希参数使用一个符号键设置图片的 alt
属性。为了能正确显示图片,应用中必须有个名为 rails.png
的图片。这个图片可以从本书的网站中下载,地址是 http://railstutorial-china.org/assets/http://railstutorial-china.org/book/images/rails.png。下载后把这个图片放在 app/assets/http://railstutorial-china.org/book/images/
文件夹中。如果使用云端 IDE 或 Unix 类系统,可以使用 curl
完成这个操作,如下所示:[5]
$ curl -O http://railstutorial-china.org/assets/http://railstutorial-china.org/book/images/rails.png
$ mv rails.png app/assets/http://railstutorial-china.org/book/images/
因为我们使用了 image_tag
辅助方法,所以 Rails 会使用 Asset Pipeline(5.2 节)自动在 app/assets/http://railstutorial-china.org/book/images/
文件夹中寻找图片。
为了更好地理解 image_tag
,我们来看一下生成的 HTML:[6]
<img alt="Rails logo" src="/assets/rails-9308b8f92fea4c19a3a0d8385b494526.png" />
其中,字符串 9308b8f92fea4c19a3a0d8385b494526
(在你的系统中得到的字符串可能不一样)由 Rails 添加,目的是确保文件名的唯一性,如果文件变化了,让浏览器重新加载文件(而不是从缓存中读取)。注意,src
属性中并没有 images
,使用的是静态文件(图片,JavaScript,CSS 等)共用的 assets
文件夹。在服务器中,Rails 会把 assets
文件夹中的图片和 app/assets/images
文件夹中的文件对应起来。这么做是为了让浏览器觉得所有静态文件都在同一个文件夹中,有利于快速伺服。alt
属性的内容会在图片无法加载时显示,例如在针对视觉障碍人士的屏幕阅读器中。
图 5.2:还没添加 CSS 的首页
现在我们终于可以看到劳动的果实了,如图 5.2 所示。你可能会说,这并不很美观啊。或许吧。不过也可以小小的高兴一下,因为我们为 HTML 结构指定了合适的类,可以用来添加 CSS。
5.1.2 Bootstrap 和自定义的 CSS
前一节我们为很多 HTML 元素指定了 CSS 类,这样我们就可以使用 CSS 灵活的构建布局了。如前所述,其中很多类在 Bootstrap 中都有特殊的意义。Bootstrap 是 Twitter 开发的框架,可以方便地把精美的 Web 设计和用户界面元素添加到使用 HTML5 开发的应用中。本节,我们会结合 Bootstrap 和一些自定义的 CSS 为演示应用添加一些样式。
首先,我们要安装 Bootstrap。在 Rails 应用中可以使用 bootstrap-sass
这个 gem,如代码清单 5.3 所示。Bootstrap 框架本身使用 LESS 编写动态样式表,而 Rails 的 Asset Pipeline 默认支持的是(非常类似的)Sass 语言。bootstrap-sass
会把 LESS 转换成 Sass,而且让 Bootstrap 中所有必要的文件都可以在当前应用中使用。[7]
代码清单 5.3:把 bootstrap-sass
添加到 Gemfile
中
source 'https://rubygems.org'
gem 'rails', '4.2.2'
gem 'bootstrap-sass', '3.2.0.0'
.
.
.
和之前一样,运行 bundle install
安装 Bootstrap:
$ bundle install
rails generate
命令会自动为控制器生成一个单独的 CSS 文件,但很难使用正确的顺序引入这些样式,所以简单起见,本书会把所有 CSS 都放在一个文件夹中。为此,我们要先新建这个 CSS 文件:
$ touch app/assets/stylesheets/custom.css.scss
(使用 3.3.3 节用过的 touch
命令,你也可以使用其他方式。)其中文件夹的名字和文件扩展名都很重要。app/assets/stylesheets/
文件夹是 Asset Pipeline 的一部分,其中所有的样式表都会引入 application.css
文件。文件名 custom.css.scss
中包含 .css
,说明这是 CSS 文件,.scss
扩展名则说明这是“Sassy CSS”文件,Asset Pipeline 会使用 Sass 处理其中的内容。(5.2.2 节才会使用 Sass,不过加入这个扩展名才能发挥 bootstrap-sass
gem 的作用。)
在这个 CSS 文件中,我们可以使用 @import
函数引入 Bootstrap(以及相关的 Sprockets 工具),如代码清单 5.4 所示。[8]
代码清单 5.4:添加 Bootstrap 的 CSS
app/assets/stylesheets/custom.css.scss
@import "bootstrap-sprockets";
@import "bootstrap";
这两行代码会引入整个 Bootstrap CSS 框架。然后重启 Web 服务器(先按 Ctrl-C 键,然后执行 rails server
命令),让这些改动生效,效果如图 5.3 所示。文本的位置还不合适,LOGO 也没有任何样式,不过颜色搭配和注册按钮看起来都不错。
图 5.3:使用 Bootstrap CSS 后的演示应用
下面我们要加入一些整站都会用到的 CSS,用来样式化网站布局和各个页面,如代码清单 5.5 所示。效果如图 5.4 所示。代码清单 5.5 中定义了很多样式规则。为了说明 CSS 规则的作用,经常会加入一些 CSS 注释,放在 /* ... */
中。
代码清单 5.5:添加全站使用的 CSS
app/assets/stylesheets/custom.css.scss
@import "bootstrap-sprockets";
@import "bootstrap";
/* universal */
body {
padding-top: 60px;
}
section {
overflow: auto;
}
textarea {
resize: vertical;
}
.center {
text-align: center;
}
.center h1 {
margin-bottom: 10px;
}
图 5.4:添加一些留白以及其他全局样式
注意,代码清单 5.5 中的 CSS 格式都是统一的。一般来说,CSS 规则通过类、ID、HTML 标签或者三者结合在一起来指代目标,然后在后面跟着一些样式声明。例如:
body {
padding-top: 60px;
}
这个规则把页面的上内边距设为 60 像素。我们在 header
标签上指定了 navbar-fixed-top
类,Bootstrap 会把这个导航条固定在页面的顶部,所以页面的上内边距会把主内容区和导航条隔开一段距离。(导航条的颜色在 Bootstrap 2.0 中变了,所以我们要加入 navbar-inverse
类,把亮色变暗。)下面的 CSS 规则:
.center {
text-align: center;
}
把 .center
类的样式定义为 text-align: center;
。.center
中的点号说明这个规则是样式化一个类。(在代码清单 5.7 中会看到,#
样式化一个 ID。)这个规则的意思是,任何类为 .center
的标签(例如 div
),其中包含的内容都会在页面中居中显示。(代码清单 5.2 中有用到这个类。)
虽然 Bootstrap 中包含了很精美的文字排版样式,我们还是要为文字的外观添加一些自定义的规则,如代码清单 5.6 所示。(并不是所有样式都用于“首页”,但所有规则都会在这个演示应用的某个地方用到。)效果如图 5.5 所示。
代码清单 5.6:添加一些精美的文字排版样式
app/assets/stylesheets/custom.css.scss
@import "bootstrap-sprockets";
@import "bootstrap";
.
.
.
/* typography */
h1, h2, h3, h4, h5, h6 {
line-height: 1;
}
h1 {
font-size: 3em;
letter-spacing: -2px;
margin-bottom: 30px;
text-align: center;
}
h2 {
font-size: 1.2em;
letter-spacing: -1px;
margin-bottom: 30px;
text-align: center;
font-weight: normal;
color: #777;
}
p {
font-size: 1.1em;
line-height: 1.7em;
}
图 5.5:添加一些排版样式
最后,我们还要为只包含文本“sample app”的网站 LOGO 添加一些样式。代码清单 5.7 中的 CSS 会把文字变成全大写字母,还修改了文字大小、颜色和位置。(我们使用的是 ID,因为我们希望 LOGO 在页面中只出现一次,不过也可以使用类。)
代码清单 5.7:添加网站 LOGO 的样式
app/assets/stylesheets/custom.css.scss
@import "bootstrap-sprockets";
@import "bootstrap";
.
.
.
/* header */
#logo {
float: left;
margin-right: 10px;
font-size: 1.7em;
color: #fff;
text-transform: uppercase;
letter-spacing: -1px;
padding-top: 9px;
font-weight: bold;
}
#logo:hover {
color: #fff;
text-decoration: none;
}
其中,color: #fff;
会把 LOGO 文字的颜色变成白色。HTML 中的颜色代码由 3 组 16 进制数组成,分别代表三原色中的红绿蓝。#ffffff
是 3 种颜色都为最大值的情况,表示纯白色。#fff
是 #ffffff
的简写形式。CSS 标准为很多常用的 HTML 颜色定义了别名,例如 white
代表 #fff
。添加代码清单 5.7 中的样式后,效果如图 5.6 所示。
图 5.6:添加 LOGO 样式后的演示应用
5.1.3 局部视图
虽然代码清单 5.1 中的布局达到了目的,但其中的内容看起来有点混乱。HTML shim 就占用了三行,而且使用了只针对 IE 的奇怪句法,如果能把它打包放在一个单独的地方就好了。头部的 HTML 自成一个逻辑单元,所以也可以把这部分打包放在某个地方。在 Rails 中我们可以使用“局部视图”实现这种想法。先来看一下定义了局部视图之后的布局文件。如代码清单 5.8 所示。
代码清单 5.8:把 HTML shim 和头部放到局部视图之后的网站布局
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
<%= render 'layouts/shim' %>
</head>
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
</div>
</body>
</html>
在这段代码中,我们把 HTML shim 删掉,换成了一行代码,调用 Rails 的辅助方法 render
:
<%= render 'layouts/shim' %>
这行代码会寻找一个名为 app/views/layouts/_shim.html.erb
的文件,执行其中的代码,然后把结果插入视图。[9](回顾一下,执行 Ruby 表达式并将结果插入模板中要使用 <%= … %>
。)注意,文件名 _shim.html.erb
的开头是个下划线,这是局部视图的命名约定,可以在目录中快速定位所有局部视图。
当然,若要局部视图起作用,我们要写入相应的内容。HTML shim 局部视图只包含三行代码,如代码清单 5.9 所示。
代码清单 5.9:HTML shim 局部视图
app/views/layouts/_shim.html.erb
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
</script>
<![endif]-->
类似地,我们可以把头部的内容移入局部视图,如代码清单 5.10 所示,然后再次调用 render
把这个局部视图插入布局中。(一般都要在文本编辑器中手动创建局部视图对应的文件。)
代码清单 5.10:网站头部的局部视图
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<%= link_to "sample app", '#', id: "logo" %>
<nav>
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Home", '#' %></li>
<li><%= link_to "Help", '#' %></li>
<li><%= link_to "Log in", '#' %></li>
</ul>
</nav>
</div>
</header>
现在我们已经知道怎么创建局部视图了,让我们来加入和头部对应的网站底部吧。你或许已经猜到了,我们会把这个局部视图命名为 _footer.html.erb
,放在布局文件夹中,如代码清单 5.11 所示。[10]
代码清单 5.11:网站底部的局部视图
app/views/layouts/_footer.html.erb
<footer class="footer">
<small>
The <a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
by <a href="http://www.michaelhartl.com/">Michael Hartl</a>
</small>
<nav>
<ul>
<li><%= link_to "About", '#' %></li>
<li><%= link_to "Contact", '#' %></li>
<li><a href="http://news.railstutorial.org/">News</a></li>
</ul>
</nav>
</footer>
和头部类似,在底部我们使用 link_to
创建到“关于”页面和“联系”页面的链接,地址先使用占位符 #
。(和 header
一样,footer
也是 HTML5 新增加的标签。)
按照 HTML shim 和头部局部视图的方式,我们也可以在布局视图中渲染底部局部视图,如代码清单 5.12 所示。
代码清单 5.12:添加底部局部视图后的网站布局
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= stylesheet_link_tag "application", media: "all",
"data-turbolinks-track" => true %>
<%= javascript_include_tag "application", "data-turbolinks-track" => true %>
<%= csrf_meta_tags %>
<%= render 'layouts/shim' %>
</head>
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
</div>
</body>
</html>
当然,如果没有样式的话,底部还很丑。底部的样式参见代码清单 5.13,效果如图 5.7 所示。
图 5.7:添加底部后的首页
代码清单 5.13:添加网站底部的 CSS
app/assets/stylesheets/custom.css.scss
.
.
.
/* footer */
footer {
margin-top: 45px;
padding-top: 5px;
border-top: 1px solid #eaeaea;
color: #777;
}
footer a {
color: #555;
}
footer a:hover {
color: #222;
}
footer small {
float: left;
}
footer ul {
float: right;
list-style: none;
}
footer ul li {
float: left;
margin-left: 15px;
}