5.6. Layouts

As mentioned in the introduction, layouts are typically shared by the entire application and wrap around a controller's view. A layout usually contains common page elements such as the header, footer, navigation, and the content area where the view's output is displayed. Layouts are often divided into separate sub-layouts. For example, the head section of your page may be one sub-layout, the global navigation another, and so forth. A single, parent layout file contains all the references to the sub-layouts. Your application can specify this parent layout using the protected $_layout_default property.

The graphic below shows the relationship between the parent layout file, represented by the outer, yellow box, and each sub-layout. The positioning of each container is typically accomplished using Cascading Style Sheets (CSS).

Figure 5.1.  A graphical representation of a layout containing several sub-layouts.

A graphical representation of a layout containing several sub-layouts

The main layout file is represented by the outer, yellow box. The _head.php section would be part of the source, and not visible on the screen.


Let's make the blog demo created in chapter one look like the example graphic above.

5.6.1. Specify the Default Layout

The first thing we need to do is specify which layout our blog application will use. Remember that the Acme_App_Blog application extends the Acme_Controller_Page class.

Browse to the SYSTEM/source/acme/Acme/Controller folder and edit the Page.php file.

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

Add the following text to the class so it looks like the code below.

<?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

You can also accomplish this by defining the protected $_layout_default property.

protected $_layout_default = 'blog';

5.6.2. Create the Default Layout

The default layout is blog.php and is located in the SYSTEM/source/acme/Acme/Controller/Page/Layout folder.

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

Copy and paste the following into the blog.php file.

<!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>

This is our main layout script. Within this layout are references to two sub-layouts, namely _head.php and _body.php.

5.6.3. Create the _head.php Sub-Layout

Now you need the _head.php. Assuming you are still in the SYSTEM/source/acme/Acme/Controller/Page/Layout folder, create the _head.php file.

$ vim _head.php

Copy and paste the following text into the file and save.

<head>
    <?php 
    // use the Solar_View_Helper_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>

This layout script is responsible for the content between the <head></head> tags. It relies on the Solar_View_Helper_Head view helper to set the main stylesheet, and display any other head elements. See the API documentation on the Solar_View_Helper_Head view helper.

5.6.4. Create the _body.php Sub-Layout

Assuming you are still in the SYSTEM/source/acme/Acme/Controller/Page/Layout folder, create the _body.php file.

$ vim _body.php

Copy and paste the following text into the file and save.

<?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>

This layout script defines the structure of the page. A cascading stylesheet will define the styles for each page element. Each element of the page, such as the header, gets it's own sub-layout script. The output from an application controller's action is injected into the layout via the $this->layout_content property.

5.6.5. Create the _header.php and _footer Sub-Layouts

Assuming you are still in the SYSTEM/source/acme/Acme/Controller/Page/Layout folder, create the _header.php file.

$ vim _header.php

Copy and paste the following text into the file and save.

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

This simple sub-layout creates the header banner at the top.

Now create the _footer.php file in the same folder.

$ vim _footer.php

Copy and paste the following text into the file and save.

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

This simple sub-layout creates the footer at the bottom.

5.6.6. Create the _nav.php and _local.php Sub-Layouts

Next, we need some sort of navigation element. Assuming you are still in the SYSTEM/source/acme/Acme/Controller/Page/Layout folder, create the _nav.php file.

$ vim _nav.php

Copy and paste the following text and save.

<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>

Notice the reference to the _local.php sub-layout. Adding this sub-layout lets us optionally add extra navigation for each controller. This will become clearer later on in this chapter.

[Note] Note

The second list item in the navigation layout uses a locale key as the link text.

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

Please see the API documentation on the Solar_View_Helper_Action helper, as well as chapter one for a brief introduction to locales.

In the same folder, create the _local.php file.

$ vim _local.php

Copy and paste the following text and save.

<?php
// placeholder for local navigation

5.6.7. Create a Stylesheet

All the layout elements are now in place. A quick view of the SYSTEM/source/acme/Acme/Controller/Page/Layout folder should show the following files:

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

Though not a requirement, prefixing partials and nested views/layouts with an underscore helps you easily identify them.

Browsing to the blog application at this point might leave you visually disappointed. In the _head.php file, we referenced a stylesheet called blog.css. This style sheet is responsible for positioning our page elements and setting fonts and colors. Let's create it now.

First, create a folder called 'styles' in the SYSTEM/source/acme/Acme/Controller/Page/Public folder.

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

Next, create a css file called blog.css in that newly created folder.

$ vim blog.css

Copy and paste the following into the blog.css file and save.

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;
}

You should now have the following:

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

The SYSTEM/source/acme/Acme/Controller/Page/Public folder is not directly accessible by the web browser. In order for the browser to find assets in the SYSTEM/source/acme/Acme/Controller/Page/Public folder, a symbolic link was defined when the Acme vendor was created. If you browse to the SYSTEM/docroot/public folder, you will see a subfolder named Acme. The web server can access the public assets via that folder.

A quick browse to the blog (http://localhost/blog) should show a screen similar to the image below.

5.6.8. Overriding Layouts

Because layouts are shared, they greatly reduce duplication of code. However, there may be times when you want to alter a page element for one particular application. The modular approach to the layout and Solar's class stack (hierarchy of classes) makes this an easy task. For example, if we wanted to have application-specific links below the main navigation, we could easily do this by overriding the _local.php layout script.

Create a new _local.php layout file in the SYSTEM/source/acme/Acme/App/Blog/Layout folder.

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

Add the following text and save.

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

Now, refresh the browser and you should notice how the left navigation has changed. Here, the original _local.php layout script in the SYSTEM/source/acme/Acme/Controller/Page/Layout folder is overridden by the _local.php layout script in SYSTEM/source/acme/Acme/App/Blog/Layout.

Solar uses the file that is most closely related to the application, then, if it doesn't find what it's looking for, will work its way up the class stack (hierarchy).

An example of the class stack for layouts would like this:

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

Note that Acme_App_Blog extends Acme_Controller_Page, which extends Solar_Controller_Page. Knowing this may help you understand how the class stack is built.

[Note] Note

You can also turn the layout off by setting $this->_layout = false in your controller, or you can change the layout altogether using $this->_layout = 'someotherlayout'.



Local