View inheritance in CakePHP

One of the new features in CakePHP 2.1 I am excited about are view blocks and view inheritance. Both are concepts borrowed from Jinja2 and other templating systems. Template inheritance allows you to create skeleton views, and define blocks to populate that skeleton in a child template. When creating a parent template you can fetch and display blocks that are populated in a child template, or view fragments defined earlier in the render flow.

When deciding to include this feature into CakePHP, I wanted to solve a few problems. The first was $scripts_for_layout. As many CakePHP veterans know $scripts_for_layout is a bit inflexible. It contains all the non-inline elements in the order they are added. This means you could end up with css files after script tags, which is less than ideal. There is also only one $scripts_for_layout variable, making it hard to split your script tags between the top and bottom of the page. By replacing $scripts_for_layout with smaller blocks, developers would have an extensible and powerful way to handle scripts/style/link tags. $content_for_layout was another legacy feature I wanted to replace with blocks. Its currently a magic variable only available in the layout. However, blocks would allow a consistent API to achieve the same thing. Lastly, the third problem I wanted to solve, was having to repeat blocks of HTML across templates, or having to use complicated element + helper setups to do things like sidebars.

Handling scripts & styles

The HtmlHelper has been retrofitted with support for blocks. All of the methods that had an 'inline' => false option, now support blocks. The default block matches the called method name. You can change the block name with the block option as well. This makes it super easy to split script files between the top and bottom of your layout.

Show Plain Text
  1. // In a view file
  2. $this->Html->script('library.js', array('block' => 'scriptTop'));
  3. $this->Html->script('ga.js', array('block' => 'scriptBottom'));
  4. ...
  5.  
  6. // In your layout
  7. <!DOCTYPE html>
  8. <html>
  9. <head>
  10.     <?php echo $this->fetch('scriptTop'); ?>
  11. </head>
  12. <body>
  13.     <?php echo $this->fetch('content'); ?>
  14.     <?php echo $this->fetch('scriptBottom'); ?>
  15. </body>
  16. </html>

From the very simple example above, you can see how blocks & HtmlHelper interact, and how the block feature gives you much more control over how assets are handled in your application. $scripts_for_layout is still around too for backwards compatibility.

Extending views and using blocks

View inheritance allows you to compose view content by defining the regions of content that change on each page, and those that don’t. View inheritance works really well when you have a generic structure that gets populated based on the context of a page. Take for example an e-commerce site. Generally they have sidebars, and related content sections. These sections are generally wrapped in some markup that provides titles and layout structure. Without view inheritance, this structural markup is repeated. As an example a simplistic parent view could look like:

Show Plain Text
  1. // app/View/Common/sidebar-page.ctp
  2.  
  3. <div class="primary">
  4.     <h2><?php echo $this->fetch('title'); ?></h2>
  5.     <div class="primary-content">
  6.         <?php echo $this->fetch('content'); ?>
  7.     </div>
  8. </div>
  9. <div class="sidebar">
  10.     <?php if ($this->fetch('related')): ?>
  11.         <div class="related">
  12.             <h3>Related content</h3>
  13.             <?php echo $this->fetch('related')); ?>
  14.         </div>
  15.     <?php endif; ?>
  16.     <?php if ($this->fetch('bestsellers')): ?>
  17.         <div class="bestsellers">
  18.             <h3>Best sellers</h3>
  19.             <?php echo $this->fetch('bestsellers'); ?>
  20.         </div>
  21.     <?php endif; ?>
  22.     <?php echo $this->element('shop/cart.ctp'); ?>
  23. </div>

Now that we have a common base view, we can re-use it across multiple sections of our site. By using view inheritance, we can save repeating the markup. Next we can create a child view for a single product. Assuming there is a $product variable with the required product data in it:

Show Plain Text
  1. // app/View/Product/view.ctp
  2.  
  3. <?php $this->extend('Common/sidebar-page.ctp'); ?>
  4. <?php $this->assign('title', h($product['Product']['name'])); ?>
  5.  
  6. <?php $this->start('related'); ?>
  7.     <p>Other items related to <?php echo h($product['Product']['name']); ?>:</p>
  8.     <?php echo $this->element('product/related', array('related' => $product['RelatedProducts']));
  9. <?php $this->end(); ?>
  10.  
  11. <p><?php echo h($product['Product']['description']); ?></p>
  12. // More product details below.

In the child template we use a few new methods on View to define the contents of blocks, and which view file is going to be this view’s parent. Using $this->extend() we can define the view we want the current one is wrapped in. Each view can have one parent, but you can make a chain as deep as necessary. $this->assign() lets you set the contents of a block to a string. This is helpful when combined with helpers, elements, or simple blocks. Next we use $this->start() and $this->end() to define the ‘related’ block. Finally, we output the rest of our view. You may have also noticed that blocks can be populated in any order. Any output in a child view, that isn’t part of another block is captured into the ‘content’ block which is available in the parent view. Since we didn’t define a ‘bestsellers’ block, that section won’t show.

I think view inheritance is a great feature to help DRY out views, and opens a few new possibilities. Applications like CMS’s have an opportunity to leverage view blocks to more easily enable modules/themes to easily interact.

Comments

Thank you for your explanation. Its really help me in understanding the new blocks features. Thanks.

zhaff on 1/29/12

Thanks ,It was great

saleh on 1/29/12

This alone will convert me to 2.x. These features are much needed, thanks for the great work as usual!

Miles Johnson on 1/29/12

Very nice new feature indeed. Thank you Mark !

nIcO on 1/30/12

Great feature! I always like this from Django!

Jmn2k1 on 1/31/12

Awesome. It’s working well right now. Is scriptBlock supported, though?

$this->Html->scriptBlock(“alert(’.’)”, array(‘block’ => ‘scriptTop’));

That doesn’t get appended to the ‘scriptTop’ block.

Patrick on 1/31/12

Patrick: Seems I missed scriptBlock(), I never really use it :) I’ll get that fixed.

mark story on 1/31/12

Thanks man! Can’t wait to use this.

Patrick on 2/1/12

Hi Mark, thank you for explanation. This really is a great new feature in CakePHP 2.1. I am waiting hangrily for the final release this branch.

Glauco on 2/3/12

This is much clearer than the docs, thanks for the post!

Elsewares on 3/14/12

Elsewares: I wrote the docs as well, what could help make the docs clearer?

mark story on 3/15/12

i’ve just used this new feature and i’m amazed with its simplicity and how powerful it is :)

damir on 3/19/12

The docs are not very clear on the usage of view blocks in where to define them.

A real life example of bringing an HTML template with multiple pages (but same layout) would help a great deal in explaining it to the new users. (I am a new user and am surely struggling with this part of Cake).

asif on 5/30/12

Is there any site where this characteristic is implemented in order to understand more easily this new features? thanks

christian on 6/8/12

Is it possible to include script into the script block from an element? I tried but it didn’t work.

Vishal on 7/2/12

How can i import/render/show one index file into other index file .. in first index file contains html form that i want to include into other index file .it is possible by any way such as rendering or extends or anything else.??????

abhishek on 1/7/14

This is my first time pay a visit at here and i am actually happy to read all at single place.

bessacarr motorhomes for sale on 1/11/14

For some reason extending views just doesn’t seem to work for me. It seems so simple so I don’t really understand why. Any tips? I have a common view author in which I want to render another view with posts by that author. I’ve extended View > Posts > author from View > Common > author. I then fetch these posts in common view author, but they just won’t display.

Eva on 2/20/15

Comments are not open at this time.