Template Inheritance and Reusability
Template reusability and inheritance mechanisms are here to boost your productivity because each template contains only its unique contents and the repeated elements and structures are reused. We introduce three concepts: layout inheritance, horizontal reuse and unit inheritance.
The concept of Latte template inheritance is similar to PHP class inheritance. You define a parent template that other child templates can extend from and can override parts of the parent template. It works great when elements share a common structure. Sounds complicated? Don't worry, it's not.
Layout Inheritance {layout}
Let’s look at layout template inheritance by starting with an example. This is a parent template which we’ll call for
example layout.latte
and it defines an HTML skeleton document.
<!doctype html>
<html lang="en">
<head>
<title>{block title}{/block}</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="content">
{block content}{/block}
</div>
<div id="footer">
{block footer}© Copyright 2008{/block}
</div>
</body>
</html>
The {block}
tags define three blocks that child templates can fill in. All the block tag does is tell the template
engine that a child template may override those portions of the template by defining their own block of the same name.
A child template might look like this:
{layout 'layout.latte'}
{block title}My amazing blog{/block}
{block content}
<p>Welcome to my awesome homepage.</p>
{/block}
The {layout}
tag is the key here. It tells the template engine that this template “extends” another template.
When Latte renderes this template, first it locates the parent – in this case, layout.latte
.
At that point, the template engine will notice the three block tags in layout.latte
and replace those blocks with
the contents of the child template. Note that since the child template didn’t define the footer block, the content from
the parent template is used instead. Content within a {block}
tag in a parent template is always used as a
fallback.
The output might look like:
<!doctype html>
<html lang="en">
<head>
<title>My amazing blog</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="content">
<p>Welcome to my awesome homepage.</p>
</div>
<div id="footer">
© Copyright 2008
</div>
</body>
</html>
In a child template, blocks can only be located either at the top level or inside another block, ie:
{block content}
<h1>{block title}Welcome to my awesome homepage{/block}</h1>
{/block}
Also a block will always be created regardless of whether the surrounding {if}
condition is evaluated to be true
or false. Contrary to what you might think, this template does define a block.
{if false}
{block head}
<meta name="robots" content="noindex, follow">
{/block}
{/if}
If you want the output inside block to be displayed conditionally, use the following instead:
{block head}
{if $condition}
<meta name="robots" content="noindex, follow">
{/if}
{/block}
Data outside of blocks in a child template are executed before the layout template is rendered, thus you can use it to define
variables like {var $foo = bar}
and propagate data to the whole inheritance chain:
{layout 'layout.latte'}
{var $robots = noindex}
...
Multilevel Inheritance
You can use as many levels of inheritance as needed. One common way of using layout inheritance is the following three-level approach:
- Create a
layout.latte
template that holds the main look-and-feel of your site. - Create a
layout-SECTIONNAME.latte
template for each section of your site. For example,layout-news.latte
,layout-blog.latte
etc. These templates all extendlayout.latte
and include section-specific styles/design. - Create individual templates for each type of page, such as a news article or blog entry. These templates extend the appropriate section template.
Dynamic Layout Inheritance
You can use a variable or any PHP expression as the name of the parent template, so inheritance can behave dynamically:
{layout $standalone ? 'minimum.latte' : 'layout.latte'}
You can also use the Latte API to choose layout template automatically.
Tips
Here are some tips for working with layout inheritance:
- If you use
{layout}
in a template, it must be the first template tag in that template. - Layout can be searched automatically (like in presenters). In this case, if the template should not
have a layout, it will indicate this with the
{layout none}
tag. - Tag
{layout}
has alias{extends}
. - The filename of the extended template depends on the template loader.
- You can have as many blocks as you want. Remember, child templates don’t have to define all parent blocks, so you can fill in reasonable defaults in a number of blocks, then only define the ones you need later.
Blocks {block}
See also anonymous {block}
A block provides a way to change how a certain part of a template is rendered but it does not interfere in any way with the logic around it. Let’s take the following example to illustrate how a block works and more importantly, how it does not work:
{foreach $posts as $post}
{block post}
<h1>{$post->title}</h1>
<p>{$post->body}</p>
{/block}
{/foreach}
If you render this template, the result would be exactly the same with or without the block tags. Blocks have access to variables from outer scopes. It is just a way to make it overridable by a child template:
{layout 'parent.Latte'}
{block post}
<article>
<header>{$post->title}</header>
<section>{$post->text}</section>
</article>
{/block}
Now, when rendering the child template, the loop is going to use the block defined in the child template
child.Latte
instead of the one defined in the base one parent.Latte
; the executed template is then
equivalent to the following one:
{foreach $posts as $post}
<article>
<header>{$post->title}</header>
<section>{$post->text}</section>
</article>
{/foreach}
However, if we create a new variable inside a named block or replace a value of existing one, the change will be visible only inside the block:
{var $foo = 'foo'}
{block post}
{do $foo = 'new value'}
{var $bar = 'bar'}
{/block}
foo: {$foo} // prints: foo
bar: {$bar ?? 'not defined'} // prints: not defined
Contents of block can be modified by filters. The following example removes all HTML and title-cases it:
<title>{block title|stripHtml|capitalize}...{/block}</title>
The tag can also be written as n:attribute:
<article n:block=post>
...
</article>
Local Blocks
Every block overrides content of parent block of the same name. Except for local blocks. They are something like private methods in class. You can create a template without worrying that – due to coincidence of block names – they would be overwritten by a second template.
{block local helper}
...
{/block}
Printing Blocks {include}
See also {include file}
To print a block in a specific place, use the {include blockname}
tag:
<title>{block title}{/block}</title>
<h1>{include title}</h1>
You can also display a block from another template:
{include footer from 'main.latte'}
Printed blocks do not have access to the variables of the active context, except if the block is defined in the same file where it is included. However they do have access to the global variables.
You can pass variables to the block in the following way:
{include footer, foo: bar, id: 123}
You can use a variable or any expression in PHP as the block name. In this case, add the keyword block
before the
variable, so that it is known at compile-time that it is a block, and not insert template,
whose name could also be in the variable:
{var $name = footer}
{include block $name}
A block can also be printed inside itself, which is useful, for example, when rendering a tree structure:
{define menu, $items}
<ul>
{foreach $items as $item}
<li>
{if is_array($item)}
{include menu, $item}
{else}
{$item}
{/if}
</li>
{/foreach}
</ul>
{/define}
Instead of {include menu, ...}
we can also write {include this, ...}
where this
means
the current block.
Printed content can be modified by filters. The following example removes all HTML and title-cases it:
<title>{include heading|stripHtml|capitalize}</title>
Parent Block
If you need to print the content of the block from the parent template, the {include parent}
statement will do the
trick. This is useful if you want to add to the contents of a parent block instead of completely overriding it.
{block footer}
{include parent}
<a href="https://github.com/nette">GitHub</a>
<a href="https://twitter.com/nettefw">Twitter</a>
{/block}
Definitions {define}
In addition to the blocks, there are also „definitions“ in Latte. They are comparable with functions in regular programming languages. They are useful to reuse template fragments to not repeat yourself.
Latte tries to keep things simple, so basically definitions are the same as blocks, and everything said about blocks also applies to definitions. They differ from blocks in that:
- they are enclosed in tags
{define}
- they are only rendered when they are inserted via
{include}
- you can define parameters for them like functions in PHP
{block foo}<p>Hello</p>{/block}
{* prints: <p>Hello</p> *}
{define bar}<p>World</p>{/define}
{* prints nothing *}
{include bar}
{* prints: <p>World</p> *}
Imagine you have a helper template with a collection of definitions on how to draw HTML forms.
{define input, $name, $value, $type = 'text'}
<input type={$type} name={$name} value={$value}>
{/define}
{define textarea, $name, $value}
<textarea name={$name}>{$value}</textarea>
{/define}
Arguments of a definition are always optional with default value null
, unless default value is specified (here
'text'
is the default value for $type
). Parameter types can also be declared:
{define input, string $name, ...}
.
The template with the definitions is loaded using {import}
. The definitions
themselves are rendered in the same way as the blocks:
<p>{include input, 'password', null, 'password'}</p>
<p>{include textarea, 'comment'}</p>
Definitions don’t have access to the variables of the active context, but they have access to global variables.
Dynamic Block Names
Latte allows great flexibility in defining blocks because the block name can be any PHP expression. This example defines three
blocks named hi-Peter
, hi-John
and hi-Mary
:
{foreach [Peter, John, Mary] as $name}
{block "hi-$name"}Hi, I am {$name}.{/block}
{/foreach}
For example, we can redefine only one block in a child template:
{block hi-John}Hello. I am {$name}.{/block}
So the output will look like this:
Hi, I am Peter.
Hello. I am John.
Hi, I am Mary.
Checking Block Existence {ifset}
See also {ifset $var}
Use the {ifset blockname}
test to check if a block (or more blocks) exists in the current context:
{ifset footer}
...
{/ifset}
{ifset footer, header, main}
...
{/ifset}
You can use a variable or any expression in PHP as the block name. In this case, add the keyword block
before the
variable to make it clear that it is not the variable that is checked:
{ifset block $name}
...
{/ifset}
The existence of blocks is also returned by the function hasBlock()
:
{if hasBlock(header) || hasBlock(footer)}
...
{/if}
Tips
Here are some tips for working with blocks:
- The last top-level block does not need to have closing tag (block ends with the end of the document). This simplifies the writing of child templates with one primary block.
- For extra readability, you can optionally give a name to your
{/block}
tag, for example{/block footer}
. However, the name must match the block name. In larger templates, this technique helps you see which block tags are being closed. - You can’t directly define multiple block tags with the same name in the same template. But this can be achieved using dynamic block names.
- You can use n:attributes to define blocks
like
<h1 n:block=title>Welcome to my awesome homepage</h1>
- Blocks can also be used without names only to apply the filters to the
output:
{block|strip} hello {/block}
Horizontal Reuse {import}
Horizontal reuse is the third mechanism for reuse and inheritance in Latte. It allows loading blocks from other templates. It
is similar to creating a file with helper functions in PHP and then loading it using require
.
While template layout inheritance is one of Latte's most powerful features, it is limited to simple inheritance – a template can only extend one other template. Horizontal reuse is a way to achieve multiple inheritance.
Let's have a set of block definitions:
{block sidebar}...{/block}
{block menu}...{/block}
Using the {import}
command, import all the blocks and definitions defined in
blocks.latte
into another template:
{import 'blocks.latte'}
{* sidebar and menu blocks can now be used *}
If you import the blocks in the parent template (i.e. use {import}
in layout.latte
), the blocks will
be available in all child templates as well, which is very handy.
The template that is intended to be imported (e.g. blocks.latte
) must not extend another template, i.e. use {layout}
. However, it can import other
templates.
The {import}
tag should be the first template tag after {layout}
. The template name can be any PHP
expression:
{import $ajax ? 'ajax.latte' : 'not-ajax.latte'}
You can use as many {import}
statements as you want in any given template. If two imported templates define the
same block, the first one wins. However, the highest priority is given to the main template, which can overwrite any
imported block.
The content of overwritten blocks can be preserved by inserting the block in the same way as a parent block:
{layout 'layout.latte'}
{import 'blocks.latte'}
{block sidebar}
{include parent}
{/block}
{block title}...{/block}
{block content}...{/block}
In this example, {include parent}
will correctly call the sidebar
block from the
blocks.latte
template.
Unit Inheritance {embed}
The unit inheritance takes the idea of layout inheritance to the level of content fragments. While layout inheritance works with “document skeletons”, which are brought to life by child templates, the unit inheritance allows you to create skeletons for smaller units of content and reuse them anywhere you like.
In unit inheritance the {embed}
tag is the key. It combines the behavior of {include}
and
{layout}
. It allows you to include another template’s or block's contents and optionally pass variables, just
like {include}
does. It also allows you to override any block defined inside the included template, like
{layout}
does.
For example, we are going to use the collapsible accordion element. Let’s take a look at the element skeleton in the
template collapsible.latte
:
<section class="collapsible {$modifierClass}">
<h4 class="collapsible__title">
{block title}{/block}
</h4>
<div class="collapsible__content">
{block content}{/block}
</div>
</section>
The {block}
tags defines two blocks that child templates can fill in. Yes, like in the case of parent template in
the layout inheritance template. You also see a $modifierClass
variable.
Let's use our element in a template. This is where {embed}
comes in. It’s a super powerful piece of kit that
lets us do all the things: include an element's template contents, add variables to it, and add blocks with custom HTML
to it:
{embed 'collapsible.latte', modifierClass: my-style}
{block title}
Hello World
{/block}
{block content}
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing
elit. Nunc dapibus tortor vel mi dapibus sollicitudin.</p>
{/block}
{/embed}
The output might look like:
<section class="collapsible my-style">
<h4 class="collapsible__title">
Hello World
</h4>
<div class="collapsible__content">
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing
elit. Nunc dapibus tortor vel mi dapibus sollicitudin.</p>
</div>
</section>
Blocks inside embed tags form a separate layer independent of other blocks. Therefore, they can have the same name as the block
outside the embed and are not affected in any way. Using the tag include inside
{embed}
tags you can insert blocks created here, blocks from an embedded template (which are not local), and also blocks from the main template which are local. You can also import blocks from other files:
{block outer}…{/block}
{block local hello}…{/block}
{embed 'collapsible.latte', modifierClass: my-style}
{import 'blocks.latte'}
{block inner}…{/block}
{block title}
{include inner} {* works, block is defined inside embed *}
{include hello} {* works, block is local in this template *}
{include content} {* works, block is defined in embedded template *}
{include aBlockDefinedInImportedTemplate} {* works *}
{include outer} {* does not work! - block is in outer layer *}
{/block}
{/embed}
Embedded templates do not have access to the variables of the active context, but they do have access to the global variables.
With {embed}
you can insert not only templates but also other blocks, so the previous example could be written
like this:
{define collapsible}
<section class="collapsible {$modifierClass}">
<h4 class="collapsible__title">
{block title}{/block}
</h4>
...
</section>
{/define}
{embed collapsible, modifierClass: my-style}
{block title}
Hello World
{/block}
...
{/embed}
If we pass an expression to {embed}
and it is not clear whether it is a block or file name, add the keyword
block
or file
:
{embed block $name} ... {/embed}
Use Cases
There are various types of inheritance and code reuse in Latte. Let's summarize the main concepts for more clarity:
{include template}
Use Case: Using header.latte
& footer.latte
inside layout.latte
.
header.latte
<nav>
<div>Home</div>
<div>About</div>
</nav>
footer.latte
<footer>
<div>Copyright</div>
</footer>
layout.latte
{include 'header.latte'}
<main>{block main}{/block}</main>
{include 'footer.latte'}
{layout}
Use Case: Extending layout.latte
inside homepage.latte
& about.latte
.
layout.latte
{include 'header.latte'}
<main>{block main}{/block}</main>
{include 'footer.latte'}
homepage.latte
{layout 'layout.latte'}
{block main}
<p>Homepage</p>
{/block}
about.latte
{layout 'layout.latte'}
{block main}
<p>About page</p>
{/block}
{import}
Use Case: sidebar.latte
in single.product.latte
& single.service.latte
.
sidebar.latte
{block sidebar}<aside>This is sidebar</aside>{/block}
single.product.latte
{layout 'product.layout.latte'}
{import 'sidebar.latte'}
{block main}<main>Product page</main>{/block}
single.service.latte
{layout 'service.layout.latte'}
{import 'sidebar.latte'}
{block main}<main>Service page</main>{/block}
{define}
Use Case: A function which gets some variables and outputs some markup.
form.latte
{define form-input, $name, $value, $type = 'text'}
<input type={$type} name={$name} value={$value}>
{/define}
profile.service.latte
{import 'form.latte'}
<form action="" method="post">
<div>{include form-input, username}</div>
<div>{include form-input, password}</div>
<div>{include form-input, submit, Submit, submit}</div>
</form>
{embed}
Use Case: Embedding pagination.latte
in product.table.latte
&
service.table.latte
.
pagination.latte
<div id="pagination">
<div>{block first}{/block}</div>
{for $i = $min + 1; $i < $max - 1; $i++}
<div>{$i}</div>
{/for}
<div>{block last}{/block}</div>
</div>
product.table.latte
{embed 'pagination.latte', min: 1, max: $products->count}
{block first}First Product Page{/block}
{block last}Last Product Page{/block}
{/embed}
service.table.latte
{embed 'pagination.latte', min: 1, max: $services->count}
{block first}First Service Page{/block}
{block last}Last Service Page{/block}
{/embed}