Page 1 of 1

Layouts and View Partials

Posted: Wed Feb 04, 2009 8:58 am
by inghamn
My usability designer the other day requested a change that I'm not able to implement using my framework the way I've written it. They want a specific view partial to show up in different positions in the layout. So, on some screens, it should be in the main panel and on other screens, they want it in panel one.

The solution I have been using is to have page controllers instantiate a template (layout) and then send blocks (view partials) to the template. All along this has assumed only one area for blocks inside of any template. The rest of the template was all interface stuff (I thought). But now, I'm trying to come up with a good way of having the page controller send blocks to various positions in the template.

Here's an example page controller for displaying a Seat in a Committee:

Code: Select all

 
$seat = new Seat($_GET['seat_id']);
$committee = $seat->getCommittee();
 
$template = new Template('two-column');
 
$template->blocks[] = new Block('committees/committeeInfo.inc',
                                array('committee'=>$committee));
$template->blocks[] = new Block('seats/seatInfo.inc',
                                array('seat'=>$seat));
 
$members = new Block('members/memberList.inc');
    $members->memberList = $seat->getMembers();
    $members->seat = $seat;
$template->blocks[] = $members;
 
echo $template->render();
 
I won't say I'm wed to this solution, but it's been working really well for the past two years and four applications, including the content manager for our website. What I like about the current solution is that each page controller can assemble a screen made out of existing blocks. And I can reuse the blocks throught the application.

But now, the question is how to get blocks to show up in different places in a template, instead of the templates having just one, single content area. It seems like the page controller is going to need to know what areas are available inside of a template.

How have some of you addressed this problem?

Re: Layouts and View Partials

Posted: Wed Feb 04, 2009 10:15 am
by inghamn
You know, I guess the real sticking point is: How is the page controller supposed to know what areas are available for any given template? Right now the actual template files are not classes, but just plain HTML files with a callback to render content.

I can see a future where we want to redo the layout of the application. Should changing a template file mean editing all the controllers that use that template? Editing the controllers would be required to make sure they're only sending content to places that template supports?

But then...if you wanted to switch a screen from a 3 column to a 2 column layout, wouldn't
it make sense to have to reorganize the content anyway? I mean, even if every template somehow published the list of available areas..( $template->getPanels() )...changing a screen to use a new template would still mean editing the controller to send content to the panels.

Is it even worth spending time trying to develop a way for the templates to communicate their available panels to the controllers?

Re: Layouts and View Partials

Posted: Wed Feb 04, 2009 11:29 am
by Christopher
I am not sure I understand the problem, but I notice that your blocks are not named - you just append them to an array. I assume that partials replace a tag in a parent template just like any other data does.

Re: Layouts and View Partials

Posted: Wed Feb 04, 2009 12:48 pm
by inghamn
arborint wrote: I am not sure I understand the problem, but I notice that your blocks are not named - you just append them to an array. I assume that partials replace a tag in a parent template just like any other data does
I just want to make sure the question is clear before I get too deep into any implementation details.

How do you move a chunk of HTML (that requires data from the controller) from one
div in the layout to another div in the same layout?

How do you swap layouts and still expect all the same chunks of HTML to show up?



Okay, now my attempt to explain my current implementation, although I'll try to leave out stuff from the implementation that does apply. That should keep the code samples shorter. That means all of these examples have been edited down to be nice and short for this discussion.

Templates hold a bunch of blocks. Templates then render a template file. Inside that template file are callbacks for the blocks. ( I think that kinda matches what you mentioned about replacing a tag in a parent template. )

Template class

Code: Select all

 
<?php
class Template extends View
{
    private $filename;
    public $blocks = array();
 
    public function __construct($filename='default')
    {
        $this->filename = $filename;
    }
 
    public function render()
    {
        ob_start();
        include APPLICATION_HOME."/templates/{$this->filename}.inc";
        return ob_get_clean();
    }
 
    /**
     * Callback function for template files
     */
    private function includeBlocks()
    {
        ob_start();
        foreach ($this->blocks as $block) {
            echo $block->render();
        }
        return ob_get_clean();
    }
}
 
Template files are written by our usability and graphics designer. They're pretty much just basic HTML.
A simple template file would look like

Code: Select all

 
<html>
<body>
    <div id="panel-container">
        <div id="panel-one">
            <h1>Left Sidebar</h1>
        </div>
        <div id="content-panel">
            <?php
                echo $this->includeBlocks();
            ?>
        </div>
    </div>
</body>
</html>
 

Okay so far so good. Here's the blocks I'm using in the Controller from my previous post.
Yes, they're view scripts that use PHP instead of a tag library, like Smarty

CommitteeInfo - requires a Committee object to be set

Code: Select all

 
$name = View::escape($this->committee->getName());
$statutoryName = View::escape($this->committee->getStatutoryName());
$statuteReference = View::escape($this->committee->getStatuteReference());
$description = View::escape($this->committee->getDescription());
 
echo "
<h1><a href=\"{$this->committee->getURL()}\">$name</a></h1>
<p>$description</p>
<table>
<tr><th>Date Formed</th>
    <td>{$this->committee->getDateFormed('n/j/Y')}</td></tr>
<tr><th>Statutory Name</th>
    <td>$statutoryName</td></tr>
<tr><th>Statute Reference</th>
    <td>$statuteReference</td></tr>
<tr><th>Website</th>
    <td><a href=\"{$this->committee->getWebsite()}\">
            {$this->committee->getWebsite()}
        </a>
    </td></tr>
</table>
";
 
SeatInfo - requires a Seat object to be set

Code: Select all

 
$title = View::escape($this->seat->getTitle());
$appointer = View::escape($this->seat->getAppointer());
$committee = View::escape($this->seat->getCommittee()->getName());
echo "
<div class=\"interfaceBox\">
    <h1>$title</h1>
    <table>
    <tr><th>Appointed By</th>
        <td>$appointer</td>
    </tr>
    <tr><th>Committee</th>
        <td>$committee</td>
    </tr>
    <tr><th>Maximum Current Members</th>
        <td>{$this->seat->getMaxCurrentMembers()}</td></tr>
    <tr><th>Requirements</th>
        <td>
";
            if ($this->seat->hasRequirements()) {
                echo '<ul>';
                foreach ($this->seat->getRequirements() as $requirement) {
                    echo '<li>'.View::escape($requirement).'</li>';
                }
                echo '</ul>';
            }
echo "
        </td>
    </tr>
    </table>
</div>
";
 
MemberList = requires a Collection of Members to be set

Code: Select all

 
<div class="interfaceBox">
    <h1>Members</h1>
    <table>
    <?php
        foreach ($this->memberList as $member) {
            $name = View::escape("{$member->getFirstname()} {$member->getLastname()}");
            echo "
            <tr><td></td>
                <td><a href=\"{$member->getURL()}\">$name</a></td>
                <td>{$member->getTerm_start('n/j/Y')} -
                    {$member->getTerm_end('n/j/Y')}
                </td>
            </tr>
            ";
        }
    ?>
    </table>
</div>
 

Re: Layouts and View Partials

Posted: Wed Feb 04, 2009 1:08 pm
by inghamn
My latest idea has been to pass a panel-name to the includeBlocks() callback. That way the template only renders any blocks it might have for that panel. This works but it's starting to highlight the sticking point I mentioned.

If I modify my template file to add an additional area to put content, I can then have the controller send blocks there. No problem, I got it working:

Code: Select all

 
<html>
<body>
    <div id="panel-container">
        <div id="panel-one">
            <h1>Left Sidebar</h1>
            <?php
                echo $this->includeBlocks('panel-one');
            ?>
        </div>
        <div id="content-panel">
            <?php
                echo $this->includeBlocks();
            ?>
        </div>
    </div>
</body>
</html>
 

This works, but it bugs me, since how is the Controller supposed to know what areas this template supports. And *should* the Controller even be deciding what content goes where? Something's just not looking right.

What I really like about the solution so far is that I don't cause any repetition. Even in the HTML. I just keep reusing the same blocks of information in the controllers. Hmmm....maybe I just need another layer or abstraction :lol: You can solve anything with another layer of abstraction, right?

Re: Layouts and View Partials

Posted: Wed Feb 04, 2009 1:13 pm
by allspiritseve
I'm not sure I exactly understand what you're trying to do, but what about instead of this:
inghamn wrote:# <div id="panel-container">
# <div id="panel-one">
# <h1>Left Sidebar</h1>
# </div>
# <div id="content-panel">
# <?php
# echo $this->includeBlocks();
# ?>
# </div>
# </div>
You do this:

Code: Select all

<div id="panel-container">
    <div id="panel-one">
        <h1><?php echo $leftbar; ?></h1>
    </div>
    <div id="content-panel">
        <?php echo $rightbar; ?>
    </div>
</div>
And then your code is responsible for what needs to be placed in those blocks, not the template. You could have several templates like this if you need different layouts, and just replace each variable with the necessary content.

Here's how I do my template class:

Code: Select all

class Template  {
 
    function __construct ($template, $array = array())  {
        $this->template = $template;
        $this->data = $array;
    }
 
    function set ($key, $value) {
        $this->data[$key] = $value;
    }
 
    function render()   {
        extract ($this->data);
        ob_start();
        include $this->template;
        return ob_get_clean();
    }
 
}
You can also nest templates:

Code: Select all

$sidebar = new Template ('templates/sidebar.tpl');
$wrapper = new Template ('templates/layoutA.tpl');
$wrapper->set ('leftbar', $sidebar->render());
$wrapper->set ('rightbar', $block->render());
echo $wrapper->render();
Maybe that will give you some ideas.

Re: Layouts and View Partials

Posted: Wed Feb 04, 2009 3:50 pm
by Chris Corbyn
I find it's easier if my views just load the layout themselves. That way you can swap out bits and pieces easily. I started to think crazy Partials and wot-not were just over-engineered concepts.

If you can have view helpers common to all pages and you can create a composite layout that isn't too clumsy to just include on each page, then you can do all of this stuff without running into such roadblocks.

I don't like it when frameworks make assumptions about my website.

Would it be much work to just simplify your overall design down to basic include()'s with some view helpers?

Re: Layouts and View Partials

Posted: Thu Feb 05, 2009 8:03 am
by inghamn
Chris Corbyn wrote: I find it's easier if my views just load the layout themselves. That way you can swap out bits and pieces easily. I started to think crazy Partials and wot-not were just over-engineered concepts.
You know, I finally realized last night, in this code I don't have one View per screen. I might not really even have a View in the sense that it's talked about on these forums. I hadn't noticed before that I was so far off from the common idea with my Templates and Block things.

How I understand Views as they seems to be discussed here:
You would have one view for each screen of the application. That view would load whatever layout it needs, it would expect certain data from the controller, and it would know all the content HTML it was going to render.

My code seems a bit off from that idea.
I don't have any Views in that sense - not the one view per screen sense. I have a small amount of Templates (5 - 7), which near as I can tell match the idea of a Layout. The controller instantiates the Template, and the controller sends Blocks of content to the Template. As far as I can tell, my Blocks are partials.

I can't say I'm violating DRY, although I have my doubts about my separation of concerns. In order to switch a screen over to a different layout, I edit the controller. I don't have a View at all, so there's only one place to go for each screen. This seems reasonable to me.

As to the other idea: if I want to change a all the screens that use a Template over to a different HTML structure, I'd have to go edit all the controllers that use that Template. This still seems reasonable to me....but that's because I don't have Views at all. If I had Views for each screen wouldn't I still need to edit each View that was using the old layout?
Chris Corbyn wrote: Would it be much work to just simplify your overall design down to basic include()'s with some view helpers?
You know I think I'm as simple as I can be right now. The only code I've not posted is my base "View" class. Which is *NOT* a View in the sense that is discussed on these forums. I named it that way back when I started doing this framework 3 years ago. Mine is just a base class that sets up an internal array for parameters with _get _set. And is an interface requiring a render() method.

Well, and I haven't posted the Models, but that's a whole different story and not really relevant here.

So, is it time for me to make normal Views like everyone else - or does my wierdness seem reasonable enough?

Re: Layouts and View Partials

Posted: Thu Feb 05, 2009 2:55 pm
by Chris Corbyn
I wouldn't say you don't have Views. You've definitely got Views and until now it has worked for you. I'd say you have more of a View system than I use myself (in terms of complexity).

But what is most flexible is just being able to load any PHP template and use that without any concept of "blocks" or "components" or "partials" or any other name given to them.

So in your page controller, something like:

Code: Select all

//Of course, you can always default to this
$this->_view->load('templates/this-action.php');
And if this-action.php just includes it's own header/footer etc you can really simplify things down:

Code: Select all

<?php include('templates/common/top.php');
<div class="container">
 
  stuff here
 
</div>
<?php include('templates/common/bottom.php'); ?>
Of course, top.php and bottom.php will have their own includes inside them and the whole template is built up this way. Now if you want something to be placed differently on one page you're in control within your template since you can just include a different set of files entirely for that page.

If you want to have a concept of "partials" where you need some back-end logic for part of the view the is re-usable on each page then just use View helpers.

I'm sure some people will disagree with me because everybody loves to have an innovative way of dealing with templates, but for me, K.I.S.S. and you'll run into less roadblocks :)