5.6 布局

简介中我们提到过,布局视图在整个应用中是共享的,它包裹控制器视图。一个布局视图通常包含一些普通的页面元素,例如:头部、底部、导航、内容区(显示视图输出)。布局视图常常被分为独立的子视图。例如,页面头部可能就是一个子视图,全局的导航又是另一个,等等。主视图包含子视图的引用。你可以使用受保护属性$_layout_default来指定应用的主视图。

下面这张图显示了主视图文件和外部容器、黄色容器及每个子视图的关系。每个容器的定位是通过层叠样式表(CSS)完成的。

Figure 5.1. 主视图包含多个子视图

主视图包含多个子视图

外部和黄色区域代表主视图。_head.php部分是页面源代码的一部分,但是它并不在屏幕上显示。

让我们把第一章创建的博客变成上图的样子。

5.6.1 指定默认布局

我们要做的第一件事就是指定博客应用使用哪个布局。记住Acme_App_Blog应用类继承自Acme_Controller_Page类。

切换至SYSTEM/source/acme/Acme/Controller目录,编辑文件Page.php

$ cd SYSTEM/source/acme/Acme/Controller/
$ vim Page.php

在类中加入以下代码,就像下面这样:

<?php 
abstract class Acme_Controller_Page extends Solar_Controller_Page
{
    /**
     * 
     * Sets up the Acme_App environment.
     * 
     * @return void
     * 
     */
    protected function _setup()
    {
        parent::_setup();
        
        // set the default layout for all applications that 
        // extend Acme_Controller_Page
        $this->_layout_default = 'blog';
    }
}
?>
[Note]Note

你也可能通过定义保护属性$_layout_default来完成这件事。

protected $_layout_default = 'blog';

5.6.2 创建默认布局

这里,默认布局是blog.php,且位于SYSTEM/source/acme/Acme/Controller/Page/Layout目录。

$ cd SYSTEM/source/acme/Acme/Controller/Page/Layout
$ vim blog.php

在默认布局中粘贴以下代码。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<?php 
// generate the <head>
include $this->template('_head.php'); 
?>

<?php 
// generate the <body>
include $this->template('_body.php'); 
?>
</html>

这是我们的主视图脚本。其中引用了2个子视图,分别是_head.php_body.php

5.6.3 创建子视图_head.php

现在我们要创建子视图_head.php。假设你仍然在SYSTEM/source/acme/Acme/Controller/Page/Layout目录下,创建_head.php文件。

$ vim _head.php

在文件中粘贴以下代码并保存文件。

<head>
    <?php 
    // use the Solar_View_Head::head() helper 
    // add a base stylesheet, then set any other head elements
    echo $this->head()->addStyleBase('Acme/Controller/Page/styles/blog.css')
                      ->fetch(); 
    ?>
</head>

这个视图脚本负责处理<head></head>标签的内容。它通过Solar_View_Helper_Head辅助类设置主样式表及添加其他头部元素。详情请查看Solar_View_Helper_Head辅助类的API文档。

5.6.4 创建子视图_body.php

假设你仍然在SYSTEM/source/acme/Acme/Controller/Page/Layout目录下,创建_body.php文件。

$ vim _body.php

在文件中粘贴以下代码并保存文件。

<?php // set the id of the body tag. Handy for css and javascript ?>
<body id="<?php echo "{$this->controller}-page" ?>">
<div id="wrap">    
    <div id="header">
        <?php include $this->template('_header.php'); ?>
    </div>

    <div id="nav">
        <?php include $this->template('_nav.php'); ?>
    </div>

    <div id="main">
        <?php // Add the content from the application controller ?>
        <?php echo $this->layout_content; ?>
    </div>

    <div id="footer">
        <?php include $this->template('_footer.php'); ?>
    </div>
</div>
</body>

视图脚本定义了页面的结构。层叠样式表定义了每个页面元素的样式。页面的每一个元素,例如页面头部(header),都有自己的子视图脚本。应用程序的控制器动作输出通过$this->layout_content属性注入到视图中。

5.6.5 创建子视图 _header.php和_footer

假设你仍然在SYSTEM/source/acme/Acme/Controller/Page/Layout目录下,创建_header.php文件。

$ vim _header.php

在文件中粘贴以下代码并保存文件。

<div id="branding">
    <h1>The Acme Blog</h1>
</div>

这个简单的子视图创建了页面顶部的横幅。

现在,仍然在这个目录下创建_footer.php文件。

$ vim _footer.php

在文件中粘贴以下代码并保存文件。

<div id="footer">
    <p>Copyright &copy; 2010 Acme</p>
</div>

这个简单的子视图创建了页面底部的页脚。

5.6.6 创建子视图_nav.php和_local.php

接下来,我们要创建导航元素。假设你仍然在SYSTEM/source/acme/Acme/Controller/Page/Layout目录下,创建_nav.php文件。

$ vim _nav.php

在文件中粘贴以下代码并保存文件。

<div id="nav">
    <ul>
        <li><?php echo $this->action('blog', 'Blog Home'); ?></li>
        <li><?php echo $this->action('blog/add', 'ACTION_ADD'); ?></li>
        <li><?php echo $this->action('blog/drafts', 'View Drafts'); ?></li>
    </ul>
    <?php // allow for extra local navigation
    include $this->template('_local.php'); 
    ?>
</div>

注意对子视图_local.php的引用。添加此子视图让我们可以选择性地为每个控制器添加额外的导航。这将在本章后面变得更清晰。

[Note]Note

导航视图中的第二个列表项使用本地键作为链接文字。

<?php echo $this->action('blog/add', 'ACTION_ADD'); ?>

请参考Solar_View_Helper_Action辅助类的API文档和第一章对本地化的介绍。

在同一个目录下创建_local.php文件。

$ vim _local.php

在文件中粘贴以下代码并保存文件。

<?php
// placeholder for local navigation

5.6.7 创建样式表

所有的布局视图都建好了。现在,在SYSTEM/source/acme/Acme/Controller/Page/Layout目录下应该有这些文件:

Acme/Controller/Page/
    Layout/
        _body.php
        _footer.php
        _head.php
        _header.php
        _local.php
        _nav.php
        blog.php
[Note]Note

尽管不是必须的,但是以下划线开始命名局部视图、嵌套视图或布局能让你迅速辨别它们。

现在你去访问博客应用的话,视觉上肯定会让你很失望。在_head.php文件中,我们引用了样式表blog.css。这个样式表负责定位页面元素、设置字体及颜色。我们现在就来创建它。

首先,在SYSTEM/source/acme/Acme/Controller/Page/Public目录下创建子目录“styles”。

$ cd ../Public
$ mkdir styles
$ cd styles

接下来,在刚建的目录下创建CSS文件blog.css

$ vim blog.css

在文件中粘贴以下代码并保存文件。

body, html {
    margin: 0;
    padding: 0;
    color: #000;
    background-color: #fff;
    font-family: sans-serif;
    font-size: 10pt;
}

#wrap {
    width: 750px;
    margin:0 auto;
    background-color: #ccc;
}

#header {
    background-color: #666;
    margin: 0px;
    color: #fff;
    margin-top: 10px;
}

#header h1 {
    margin: 0;
    padding: 10px;
}

#nav {
    background-color: #ccc;
    width: 150px;
    float: left;
}

#main {
    float: left;
    width: 580px;
    background-color: #efefef;
    padding: 10px;
    min-height: 400px;
}

#main h2 {
    margin-top: 0px;
    padding-top: 0px;
}

#footer {
    background-color: #000;
    color: #fff;
    font-weight: bold;
    clear: both;
    padding: 5px;
}

#footer p {
    padding: 0px;
    margin: 0px;
}

我们刚才做的事如下:

Acme/Controller/Page/
    Public/
        styles/
            blog.css
[Note]Note

SYSTEM/source/acme/Acme/Controller/Page/Public目录是不能被直接访问的。为了浏览SYSTEM/source/acme/Acme/Controller/Page/Public目录的附件,在Acme工作区创建的时定义了一个符号链接(symlink)。如果你浏览SYSTEM/docroot/public目录,你将会发现子目录Acme。通过这个目录,公共附件就能够被访问了。

现在我们来看一下博客的首页(http://localhost/blog),应该和下图一样:

5.6.8 覆盖布局

因为布局是共享的,它们极大地减少了代码的重复。但是有时候在某些特殊应用中,你可能想更换页面的某些元素。模块化方式布局和Solar的类栈(类层次)机制使这种任务非常简单。例如,如果我们想在主导航下面创建某个特殊应用的链接,我们只要简单的覆盖_local.php布局脚本即可。

SYSTEM/source/acme/Acme/App/Blog/Layout目录下创建新文件_local.php

$ cd SYSTEM/source/acme/Acme/App/Blog/Layout
$ vim _local.php

在文件中粘贴以下代码并保存文件。

<ul>
    <li><a href="#">Local Item One</a></li>
    <li><a href="#">Local Item Two</a></li>
</ul>

现在,刷新浏览器,你将看到左边导航发生了变化。在这里,原先在SYSTEM/source/acme/Acme/Controller/Page/Layout目录下的_local.php文件被SYSTEM/source/acme/Acme/App/Blog/Layout目录下的_local.php文件覆盖。

Solar使用和应用相关度最大的文件。如果它没有找到它要找的信息,它将会继续在类栈(层次)中向上查找。

下面是一个布局类栈的示例:

Array
(
    [0] => Acme/App/Blog/Layout
    [1] => Acme/Controller/Page/Layout
    [2] => Solar/Controller/Page/Layout
)

注意到Acme_App_Blog继承自Acme_Controller_Page,而Acme_Controller_Page又继承自Solar_Controller_Page。知道这个将帮助你理解Solar的类栈是如何构建的。

[Note]Note

你可以在控制器中设置$this->_layout = false关闭布局功能,你也可以设置$this->_layout = 'someotherlayout'改变布局。