Play Framework Web开发教程(33): 结构化页面-组合使用模板

和你编写代码类似,你编写的页面也可以由多个小的片段组合而成,这些小的片段本身也可以由更小的片段构成。这些小片段通常是可以在其它页面重复使用的:有些部分可以用在所有页面,而有些部分是某些页面特定的。本篇介绍如何使用这些可重用的小模板来构成整个页面。
Includes
到目前为止的例子,我们只显示了HTML的片段,没有实例显示整个页面。下面我们给出完整的显现产品列表的代码和模板:

1def catalog() = Action {
2    val products = ProductDAO.list
3    Ok(views.html.shop.catalog(products))
4}

对应的页面模板app/views/products/catalog.scala.html

1@(products: Seq[Product])
2<!DOCTYPE html>
3<html>
4    <head>
5        <title>paperclips.example.com</title>
6        <link href="@routes.Assets.at("stylesheets/main.css")"
7        rel="stylesheet">
8    </head>
9    <body>
10        <div id="header">
11            <h1>Products</h1>
12        </div>
13        <div id="navigation">
14            <ul>
15                <li><a href="@routes.Application.home">Home</a></li>
16                <li><a href="@routes.Shop.catalog">Products</a></li>
17                <li><a href="@routes.Application.contact">Contact</a></li>
18            </ul>
19        </div>
20        <div id="content">
21            <h2>Products</h2>
22            <ul class="products">
23            @for(product <- products) {
24                <li>
25                    <h3>@product.name</h3>
26                    <p class="description">@product.description</p>
27                </li>
28            }
29            </ul>
30        </div>
31        <footer>
32            <p>Copyright paperclips.example.com</p>
33        </footer>
34    </body>
35</html>

这样我们就定义了一个完整的HTML页面,但是我们在其中添加了不少和显示产品列表不直接相关的标记,比如Catalog不需要知道菜单是如何显示的。页面模块化和重用性不高。
一般来说,一个action方法只应负责最终页面的内容部分。对于很多网站来说,页头,页脚,导航条在不同页面是通用的,如下图:

20141006001

在这个页面样式中,Header,Navigation,Footer通常是不变的,需要变化的部分是由Page Content指定的部分。

因此我们可以把之前产品列表页面模板中的导航条部分抽取出来单独定义一个navigation页面模板:
views/navigation.scala.html

1@()
2<div id="navigation">
3    <ul>
4        <li><a href="@routes.Application.home">Home</a></li>
5        <li><a href="@routes.Shop.catalog">Catalog</a></li>
6        <li><a href="@routes.Application.contact">Contact</a></li>
7    </ul>
8</div>

然后修改之前的catelog.scala.html

1@(products: Seq[Product])
2<!DOCTYPE html>
3<html>
4    <head>
5        <title>paperclips.example.com</title>
6        <link href="@routes.Assets.at("stylesheets/main.css")"
7        rel="stylesheet">
8    </head>
9    <body>
10        <div id="header">
11            <h1>Products</h1>
12        </div>
13        @navigation()
14        <div id="content">
15            <h2>Products</h2>
16            <ul class="products">
17            @for(product <- products) {
18                <li>
19                    <h3>@product.name</h3>
20                    <p class="description">@product.description</p>
21                </li>
22            }
23            </ul>
24        </div>
25        <footer>
26            <p>Copyright paperclips.example.com</p>
27        </footer>
28    </body>
29</html>

这个修改使得我们的页面变得更好,因为Catalog页面无需再知道如何显示导航条,这种把部分页面模板抽取出来单独写成一个可重复使用页面模板的方法叫”includes”,而抽取出来的模板叫”include”。

Layouts
前面的include使得我们的页面模板变好了,但是还是有改进的余地。我们可以看到Catelog页面还是显示整个页面,比如HTML DocType,head等等,这部分不应该由Catalog来负责显示。前面页面模板只有div content部分由Catalog来显示:

1<h2>Products</h2>
2<ul class="products">
3@for(product <- products) {
4    <li>
5        <h3>@product.name</h3>
6        <p class="description">@product.description</p>
7    </li>
8}
9</ul>

其它部分都应该放在Catalog 模板之外。我们也可以使用之前的include技术,但不是最理想的。如果我们使用”include”技术,那么我们需要另外两个新的模板,一个为Content前面部分的内容,另外一个模板为Content后面部分的内容。这种方法不是很好,因为这些内容应该是放在一起的。
幸运的是使用Scala的组合功能,Play支持抽出所有的内容到一个模板中,从catalog.scala.html 模板中抽出所有不应由catalog负责的部分,到一个布局模板:

1<!DOCTYPE html>
2<html>
3    <head>
4        <title>paperclips.example.com</title>
5        <link href="@routes.Assets.at("stylesheets/main.css")"
6        rel="stylesheet">
7    </head>
8    <body>
9        <div id="header">
10            <h1>Products</h1>
11        </div>
12        @navigation()
13        <div id="content">
14          // Content here
15        </div>
16        <footer>
17            <p>Copyright paperclips.example.com</p>
18        </footer>
19    </body>
20</html>

我们把这部分模板存放在app/views/main.scala.html中,要使得这个模板变成可以重用的,我们为它定义一个参数content,其类型为html,修改如下:

1@(content: Html)
2<!DOCTYPE html>
3<html>
4    <head>
5        <title>paperclips.example.com</title>
6        <link href="@routes.Assets.at("stylesheets/main.css")"
7        rel="stylesheet">
8    </head>
9    <body>
10        <div id="header">
11            <h1>Products</h1>
12        </div>
13        @navigation
14        <div id="content">
15        @content
16        </div>
17        <footer>
18            <p>Copyright paperclips.example.com</p>
19        </footer>
20    </body>
21</html>

使用这个模板如同调用Scala函数类型,views.html.main(content) ,使用这个布局模板,我们修改catelog页面如下:

1@(products: Seq[Product])
2@main {
3    <h2>Products</h2>
4    <ul class="products">
5    @for(product <- products) {
6        <li>
7            <h3>@product.name</h3>
8            <p class="description">@product.description</p>
9            }
10    </ul>
11}

如果有需要,我们可以为main布局模板添加更多的参数,比如将title也作为参数,
比如把

1@(content: Html)
2<html>
3    <head>
4    <title>Paper-clip web shop</title>

修改成

1@(title: String)(content: Html)
2<html>
3    <head>
4    <title>@title</title>

还可以给参数定义缺省值,比如:

1@(title="paperclips.example.com")(content: Html)

这样修改可以进一步参数化布局模板,通用性更强。

更多Play教程请访问http://www.imobilebbs.com/


阅读更多
换一批