Freewind @ Thoughtworks scala java javascript dart 工具 编程实践 月结 math python english [comments admin] [feed]

(2013-01-01) 3. 构建第一个视图

广告: 云梯:翻墙vpn (省10元) 土行孙:科研用户翻墙http proxy (有优惠)

(本文译者是CQS)

构建第一个页面

现在我们已经建立了第一个数据模型,是时候去创建第一个应用页面了.这个页面将展示最新发的博文,同时也展示了旧的博文链接列表.

下面这张图片就是我们要看的页面效果原型:

image

用默认的数据引导程序


事实上,在写第一个页面前,我们需要做件事情先.写一个没有测试数据的网页应用是不爽的.你不能对你的应用做任何测试.因为我们不曾创建了博文发布的页面,所以我们不能自己发布博文.

为了得到数据,我们可以在应用载入的时候,去载入一个fixture文件并读取默认的数据。为了执行这个操作,我们新建一个引导任务(Bootstrap Job).一个Play任务(Job)即在任何Http 请求外执行一些操作,比如说在应用起始时或者间断性地执行周期任务.

现在让我们创建/yabe/app/Bootstrap.java任务去使用Fixtures载入默认数据.

import play.*;
import play.jobs.*;
import play.test.*;

import models.*;

@OnApplicationStart
public class Bootstrap extends Job {

public void doJob() {
    // Check if the database is empty
        if(User.count() == 0) {
        Fixtures.loadModels("initial-data.yml");
        }
    }
}

我们用<code>@OnApplicationStart</code>去注释这个Job是为了告诉Play在应用启动时并同步的执行这个任务.

> 事实上,Play 任务在DEV 和PROD 模式下是不同的.在Dev模式下,Play会等待第一个请求,才会启动应用.所以这个任务也会在第一次请求是同步执行.这种情况下,如果你的任务执行失败,你将在浏览器得到错误信息。在PROD模式下,这个任务会在应用开始就被执行(和`play run`命令同步)同时Play会在发生错误时阻止应用启动.

你需要在`/yabe/conf`目录下创建一个`initial-data.yml`文件.当然你也可以重新使用我们之前用来测试的数据内容`data.yml`.

现在用`play run`命令启动这个应用吧,在浏览器输入[http://localhost:9000/](http://localhost:9000/)查看页面.

#### 构建博客主页

* * *

这次,我们可是要真开始编写博客主页了.

你还记得第一个页面如何显示的吗?先在routes配置文件中规定`/` URL地址去调用`controller.Application.index()`action方法.然后这个方法会调用`render()`并执行`/yabe/app/views/Application/index.html`模板.

我们将保留这些组件,并填添加代码,让它们载入博文列表并展示.

打开`/yabe/app/controllers/Application.java`这个控制器(controller)文件并修改这个`index()`方法去载入博文列表,如下代码:

package controllers;

import java.util.*;

import play.*;
import play.mvc.*;

import models.*;

public class Application extends Controller {

    public static void index() {
        Post frontPost = Post.find("order by postedAt desc").first();
        List<Post> olderPosts = Post.find(
        "order by postedAt desc"
        ).from(1).fetch(10);
        render(frontPost, olderPosts);
    }

}

你看见如何传递对象给`render()`方法了吗?它允许对应的模板文件使用同名去读取对象。就这个例子而言,变量`frontPost`和`olderPosts`即可以在模板中使用.

打开模板`/yabe/app/views/Application/index.html`并修改去展示这些对象.

#{extends 'main.html' /}
#{set title:'Home' /}

#{if frontPost}
    <div class="post">
    <h2 class="post-title">
        <a href="#">${frontPost.title}</a>
    </h2>
    <div class="post-metadata">
        <span class="post-author">by ${frontPost.author.fullname}</span>
        <span class="post-date">${frontPost.postedAt.format('MMM dd')}</span>
        <span class="post-comments">
            &nbsp;|&nbsp; 
            ${frontPost.comments.size() ?: 'no'} 
            comment${frontPost.comments.size().pluralize()}
            #{if frontPost.comments}
                , latest by ${frontPost.comments[-1].author}
            #{/if}
        </span>
    </div>
    <div class="post-content">
        ${frontPost.content.nl2br()}
    </div>
</div>

#{if olderPosts}
    <div class="older-posts">    
        <h3>Older posts <span class="from">from this blog</span></h3>

        #{list items:olderPosts, as:'oldPost'}
            <div class="post">
                <h2 class="post-title">
                    <a href="#">${oldPost.title}</a>
                </h2>
                <div class="post-metadata">
                    <span class="post-author">
                        by ${oldPost.author.fullname}
                    </span>
                    <span class="post-date">
                        ${oldPost.postedAt.format('dd MMM yy')}
                    </span>
                    <div class="post-comments">
                        ${oldPost.comments.size() ?: 'no'} 
                        comment${oldPost.comments.size().pluralize()}
                        #{if oldPost.comments}
                            - latest by     ${oldPost.comments[-1].author}
                        #{/if}
                    </div>
                </div>
            </div>
        #{/list}
    </div>

#{/if}

#{/if}

#{else}
<div class="empty">
    There is currently nothing to read here.
</div>
#{/else}

你可以在Templates章节查看下模板是如何工作的.基本上,它允许你动态读取Java对象.我们使用Groovy驱动模板引擎.大部分简洁的构建正如你所见的如`?:`操作,都是来自Groovy.但是你并不需要去了解如何用Groovy写Play模板.如果你已经对其他模板语言例如JSP(用JSTL驱动)够熟悉的话,你就不用慌,够用了.

好的,现在可以刷新你的博客主页了.

[![image](/user_images/1308-3.png "image")](/user_images/1308-3.png)

虽不靓,但已跑起.

不爽的是你发现你已经在码重复的代码了.因为我们展示多个形式的展示博文(完整的,完整带评论,博文摘要),我们将创建一个类函数的东西可以被多个模板调用.这个东西做的便是Play tag的职能。

创建一个标签,只需要在`/yabe/app/views/tags/`下创建一个`display.html`文件.标签其实另一种模板.它含有参数(像一个函数).这个`#{display/}`标签有两个参数:一个是要被展示的Post对象,另个是展示的模式(`home`,`teaser`,`full`三者之一).

*{ Display a post in one of these modes: 'full', 'home' or 'teaser' }*

<div class="post ${_as == 'teaser' ? 'teaser' : ''}">
<h2 class="post-title">
    <a href="#">${_post.title}</a>
</h2>
<div class="post-metadata">
    <span class="post-author">by ${_post.author.fullname}</span>,
    <span class="post-date">${_post.postedAt.format('dd MMM yy')}</span>
    #{if _as != 'full'}
        <span class="post-comments">
            &nbsp;|&nbsp; ${_post.comments.size() ?: 'no'} 
            comment${_post.comments.size().pluralize()}
            #{if _post.comments}
                , latest by ${_post.comments[-1].author}
            #{/if}
        </span>
    #{/if}
</div>
#{if _as != 'teaser'}
    <div class="post-content">
        <div class="about">Detail: </div>
        ${_post.content.nl2br()}
    </div>
#{/if}
</div>

#{if _as == 'full'}
<div class="comments">
    <h3>
        ${_post.comments.size() ?: 'no'} 
        comment${_post.comments.size().pluralize()}
    </h3>

    #{list items:_post.comments, as:'comment'}
        <div class="comment">
            <div class="comment-metadata">
                <span class="comment-author">by ${comment.author},</span>
                <span class="comment-date">
                    ${comment.postedAt.format('dd MMM yy')}
                </span>
            </div>
            <div class="comment-content">
                <div class="about">Detail: </div>
                ${comment.content.escape().nl2br()}
            </div>
        </div>
    #{/list}

</div>
#{/if}

现在就可以使用Play标签去重写刚才那带有重复代码的主页了.

#{extends 'main.html' /}
#{set title:'Home' /}

#{if frontPost}

#{display post:frontPost, as:'home' /}

#{if olderPosts.size()}

    <div class="older-posts">
        <h3>Older posts <span class="from">from this blog</span></h3>

        #{list items:olderPosts, as:'oldPost'}
            #{display post:oldPost, as:'teaser' /}
        #{/list}
    </div>

#{/if}

#{/if}

#{else}
<div class="empty">
    There is currently nothing to read here.
</div>
#{/else}

刷新一下页面,页面OK.

#### 改善布局

* * *

正如你所见,`index.html`模板从`main.html`处继承。因为我们要提供一个通用的布局给所有带有博文标题和登录链接的博文页面,我们需要修改`main.html`文件。 编辑这个`/yable/app/views/main.html`文件

<!DOCTYPE html >
<html>
<head>
    <title>#{get 'title' /}</title>        
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <link rel="stylesheet" type="text/css" media="screen" 
        href="@{'/public/stylesheets/main.css'}" />
    <link rel="shortcut icon" type="image/png" 
        href="@{'/public/images/favicon.png'}" />
</head>
<body>

    <div id="header">
        <div id="logo">
            yabe.
        </div>
        <ul id="tools">
            <li>
                <a href="#">Log in to write something</a>
            </li>
        </ul>
        <div id="title">
            <span class="about">About this blog</span>
            <h1><a href="#">${blogTitle}</a></h1>
            <h2>${blogBaseline}</h2>
        </div>
    </div>

    <div id="main">
        #{doLayout /} 
    </div>

    <p id="footer">
        Yabe is a (not that) powerful blog engine built with the 
        <a href="http://www.playframework.org">Play framework</a>
        as a tutorial application.
    </p>

</body>
</html>

刷新并确认结果.发现这个页面Ok了,除了`blogTitle`和`blogBaseline`这两个变量没有被正确显示。因为我们没有在`render(...)`中传递它们。当然我们可以在`index`action方法中的`render()`中添加.因为`main.html`文件被作为其他所有方法模板的基本模板,我并不乐意在每天调用时都传递这两个变量.

有一种方法可以执行同样的效果,即让contoller文件中的每个action都做同样的操作,只需定义一个`@Before`拦截方法。

我们添加一个`addDefaults()`方法到Application controller文件.

@Before
static void addDefaults() {
    renderArgs.put("blogTitle", Play.configuration.getProperty("blog.title"));
    renderArgs.put("blogBaseline", Play.configuration.getProperty("blog.baseline"));
}
> 你需要在`Application.java`文件中导入`play.Play`.

在`renderArgs`范围中添加的所有变量都可以被模板获取。你会发现这个方法从`Play.configuration`对象中读取这些变量值.而这个对象包含了`/yabe/conf/application.conf`文件中所有配置的键值(key).

在这个配置文件中添加两个键值:

# Blog engine configuration
# ~~~~~
blog.title=Yet another blog
blog.baseline=We will write about nothing

重新载入这个主页,确认它Ok了.

[![image](/user_images/1308-5.png "image")](/user_images/1308-5.png)

#### 添加一些风格

* * *

现在这个博客主页差不多了,但是它并不美观。我们需要添加一些风格让它更美观些.你会发现这个main 模板文件`main.html`包含了`/public/stylesheets/maim.css`样式表.我们将保留这句话,并在CSS文件中添加样式.

你可以点击[这里](http://shuzu.org:9000/play/books/tutorial/articles/www.playframework.org/documentation/1.2.5/files/main.css)下载,然后复制内容到`/public/stylesheets/main.css`文件下.

刷新这个页面,你就可以看到样式化的页面了.

[![image](/user_images/1308-7.png "image")](/user_images/1308-7.png)

#### 提交工作

* * *

现在这个主页完成了。和往常一样,我们需要提交这个博客版本到bazaar:

$ bzr st
$ bzr add
$ bzr commit -m "Home page"
comments powered by Disqus