Introducing the 0.20 Layouts System - Part I

Posted Jun 30, 2009 by Kris Jordan | Comments ( 5 ) | Filed in: Views | | | | |

One of the biggest features going into Recess 0.20 is the new layouts system for views and display logic largely inspired by Joshua Paine's original work. It has been in the making since February and, with the latest changeset on GitHub, the view system is finally there after going through 3 major iterations. For those who have been following along and familiarized yourselves with the existing slots/blocks system I want to take some time to explain how the final system works. It is a simplification of the slots/blocks scheme with some additional new functionality.

In Recess 0.20 and beyond view code will revolve around 2 fundamental concepts: blocks and assertive templates which drive layouts and parts.

Buffering to Blocks

Blocks are objects that contain unrendered output. Think of a Block as a hunk of HTML you can pass around and manipulate before rendering. Block is an abstract class with multiple sub-class implementations. You can create a simple block by instantiating HtmlBlock:

<?
$block = new HtmlBlock(
    "<h1>Hello World</h1>" .
	"<p>Welcome to Recess 0.20</p>"
);
echo $block;
?>

The result of this code would be the contents sent to output. If you saw the previous iteration of blocks and slots you may be thinking, "Why write HTML in strings? Isn't the point of Blocks not to do that?" One of the realizations in the last view iteration was that Blocks were confounding two orthogonal concerns: 1) conveniently buffering output and 2) holding onto the output for later use. We broke out these two responsibilities so now Blocks are concerned with retaining output and the Buffer helper is concerned with buffering output to a block. The Buffer fills Blocks. Let's take a look:

<? Buffer::to($block) ?>
   <h1>Hello World</h1>
   <p>Welcome to Recess 0.20</p>
<? Buffer::end ?>
<?= $block ?>

With more content being buffered and/or complex display logic the benefits of buffering output to blocks becomes much more convenient. You can append to, prepend to, and overwrite the content of the resulting blocks with methods on the block instance or using the Buffer helpers.

Layout, an Assertive Template

Most web applications share HTML between many different views. PHP has a simple way of including common HTML between scripts: the include statement. It is not uncommon to see include('header.php'), include('footer.php'), etc. The downside to using this method of including scripts is that it introduces structural dependencies throughout all of your view code. Recess' inverts this dependency problem with layouts. A view script can extend a single layout.

What is a layout? A layout is a template with defined inputs. A layout uses the inputs, provided by a child template extending the layout, to fill in slots of HTML. Let's take a look at a simple example (simple.layout.php):

<?php/*Input Variable   Type      Default Value
--------------------------------------------------------*/
Layout::input($title,   'string', 'The Resilient Layout');
Layout::input($sidebar, 'Block');
Layout::input($body,    'Block');
?>
<html>
	<head>
		<title><?= $title ?></title>
	</head>
	<body>
		<div id="container">
			<div id="sidebar">
			<? if(!$sidebar->draw()): ?>
				<ul>
				  <li>Default Link A</li>
				  <li>Default Link B</li>
				  <li>Default Link C</li>
				</ul>
			<? endif ?>
			</div>
			<div id="content">
			<? if(!$body->draw()): ?>
				<p>Default content.</p>
			<? endif ?>
			</div>
		</div>
	</body>
<? 
if(isset($die)) {
	die('By accident or affliction, a child template cannot kill me'
		. ', for I will never take $die as an input.');
}
?>
</html>

Notice Blocks have a draw method. If the Block contains output it will be printed and return true, else it will return false. For Recess Edge followers, the if(!$block->draw){} construct is equivalent to the previous Layout::slot('block'); Layout::slotEnd(); mechanism.

Also notice in the layout we define our input variables, their respective types, and any default values. This makes the layout an Assertive Template. Assertive templates declare their inputs. You may be wondering, why does the parent layout not simply inherit the entire context of the child? What justifies the extra typing?

  • Design by Contract - Layouts define an input contract. If your child template does not fulfill the contract Recess will immediately fail and tell you the error is in the child template not providing an input. Compare this to the obscure '$foo is not defined in parent.php at line 305' error message you would otherwise receive: Is the bug in the child script or the parent layout? You would have to understand the logic of both to pinpoint the problem.
  • Fewer Surprises - The only variables that exist in a parent layout are those declared as inputs. If the input requirements aren't met the child template is to blame, if the input requirements are met, then the parent layout is to blame. To reason about the behavior of an assertive template you only need to reason about one script at a time.
  • Self Documenting - What variables does a layout expect? With assertive templates this is easy: just look at the inputs. Without assertive templates this can be a pain: you must completely understand all of the code in another script.

We can extend the simple layout in a view template with the following code:

<?
Layout::extend('simple')
$title = 'A Simple Layout'
$die = true; // Has no effect.
?>

<? Buffer::to($sidebar): ?>
<ul>
 <li class="selected">
  <?=html::anchor(url::action('Controller::method'),'Link 1')?>
 </li>
 <li>
  <?=html::anchor(url::action('Controller::method2'),'Link 2')?>
 &;/li>
 <li>
   <?=html::anchor(url::action('Controller::method3'),'Link 3')?>
 </li>
</ul>
<? Buffer::end() ?>

<h1>Body</h1>
<p>$body is a special Block that is created implicitly 
   if not defined explicitly.</p>
 

By using the Layout helper's extend method we declare that we're inheriting from simple.layout.php. Once the child template has executed the variables declared as inputs to the parent layout will be extracted from the child template and passed to the parent. There are two Block inputs in the layout: $sidebar and $body. The child template uses the Buffer technique described above to fill the $sidebar block. The $body block is filled by a special case: any output in a child template will automatically fill a block named $body, unless the child template defines $body explicitly.

Notice that we assign true to variable $die in the child, and in the parent there is a conditional that will kill the script if $die is set. The parent layout will not die, though, because $die is not an input of the layout. Layouts only acquire the scope their inputs they define! This is, admittedly, a contrived example.

In Part II of this look at Recess 0.20's new view system we will cover Parts, Recess' spin on partial templates, and the powerful & mystical Block that is a Part: PartBlock.

Comments

Recess can only be as good as the thoughts that go into it. Let us hear yours...

  1. Posted by Mark on Jul 1, 2009
    Nice, you have some pretty sick ideas right there! I'm using code igniter and I must say it's frustrating to see the lack of templates in the core.
  2. Posted by Pelle on Jul 1, 2009
    Really impressed by your work! Will certainly try to get time to try and build a site on 0.20 when it's released
  3. Posted by q on Jul 30, 2009
    Why was my comment about Recess needing a new logo and website design deleted? It was a fair suggestion, but the removal just confirms some of the doubts I have about the framework being controlled by one developer.
  4. Posted by Kris on Aug 3, 2009
    @q - Apologies if your comment got deleted or caught in the spam filter. The blog gets over 1000 spam comments a week and a best effort is made to keep the spam away. If you are interested in joining the team and contributing to the logo / web design that'd be great.
  5. Posted by falmp on Nov 1, 2009
    Nice template system, can't wait for the following posts about Parts and PartBlocks.