Herança e Reutilização de Templates
Os mecanismos de reutilização e herança de templates aumentarão sua produtividade, pois cada template contém apenas seu conteúdo único, e elementos e estruturas repetidos são reutilizados. Apresentamos três conceitos: Herança de layout, Reutilização horizontal e Herança de unidade.
O conceito de herança de templates Latte é semelhante à herança de classes em PHP. Você define um template pai, do qual outros templates filhos podem herdar e podem substituir partes do template pai. Funciona muito bem quando os elementos compartilham uma estrutura comum. Parece complicado? Não se preocupe, é muito fácil.
Herança de Layout {layout}
Vamos ver a herança de template de layout com um exemplo. Este é um template pai, que chamaremos, por exemplo, de
layout.latte
, e que define o esqueleto de um documento HTML:
<!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>
As tags {block}
definem três blocos que os templates filhos podem preencher. A tag block apenas informa que este
local pode ser substituído por um template filho, definindo seu próprio bloco com o mesmo nome.
Um template filho pode parecer com isto:
{layout 'layout.latte'}
{block title}My amazing blog{/block}
{block content}
<p>Welcome to my awesome homepage.</p>
{/block}
A chave aqui é a tag {layout}
. Ela diz ao Latte que este template “estende” outro template. Quando o Latte
renderiza este template, ele primeiro encontra o template pai – neste caso, layout.latte
.
Neste ponto, o Latte percebe as três tags de bloco em layout.latte
e substitui esses blocos pelo conteúdo do
template filho. Como o template filho não definiu o bloco footer, o conteúdo do template pai é usado em seu lugar.
O conteúdo dentro da tag {block}
no template pai é sempre usado como fallback.
A saída pode parecer com isto:
<!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>
No template filho, os blocos podem ser colocados apenas no nível superior ou dentro de outro bloco, ou seja:
{block content}
<h1>{block title}Welcome to my awesome homepage{/block}</h1>
{/block}
Além disso, um bloco sempre será criado, independentemente de a condição {if}
circundante ser avaliada como
verdadeira ou falsa. Portanto, mesmo que não pareça, este template definirá o bloco.
{if false}
{block head}
<meta name="robots" content="noindex, follow">
{/block}
{/if}
Se você quiser que a saída dentro do bloco seja exibida condicionalmente, use o seguinte em vez disso:
{block head}
{if $condition}
<meta name="robots" content="noindex, follow">
{/if}
{/block}
O espaço fora dos blocos no template filho é executado antes da renderização do template de layout, então você pode
usá-lo para definir variáveis como {var $foo = bar}
e propagar dados por toda a cadeia de herança:
{layout 'layout.latte'}
{var $robots = noindex}
...
Herança Multinível
Você pode usar quantos níveis de herança precisar. Uma maneira comum de usar a herança de layout é a seguinte abordagem de três níveis:
- Crie um template
layout.latte
que contenha o esqueleto principal da aparência do site. - Crie um template
layout-SECTIONNAME.latte
para cada seção do seu site. Por exemplo,layout-news.latte
,layout-blog.latte
, etc. Todos esses templates estendemlayout.latte
e incluem estilos e design específicos para cada seção. - Crie templates individuais para cada tipo de página, como um artigo de notícias ou uma postagem de blog. Esses templates estendem o template da seção correspondente.
Herança Dinâmica
Como nome do template pai, pode-se usar uma variável ou qualquer expressão PHP, de modo que a herança pode se comportar dinamicamente:
{layout $standalone ? 'minimum.latte' : 'layout.latte'}
Você também pode usar a API Latte para selecionar automaticamente o template de layout.
Dicas
Aqui estão algumas dicas para trabalhar com herança de layout:
- Se você usar
{layout}
em um template, deve ser a primeira tag do template nesse template. - O layout pode ser pesquisado
automaticamente (como em presenters). Nesse
caso, se o template não deve ter um layout, ele o indica com a tag
{layout none}
. - A tag
{layout}
tem um alias{extends}
. - O nome do arquivo de layout depende do loader.
- Você pode ter quantos blocos quiser. Lembre-se de que os templates filhos não precisam definir todos os blocos pais, então você pode preencher valores padrão razoáveis em vários blocos e, em seguida, definir apenas aqueles que precisar mais tarde.
Blocos {block}
Veja também o anônimo {block}
Um bloco representa uma maneira de alterar como uma parte específica de um template é renderizada, mas não interfere na lógica ao redor dele. No exemplo a seguir, mostraremos como um bloco funciona, mas também como não funciona:
{foreach $posts as $post}
{block post}
<h1>{$post->title}</h1>
<p>{$post->body}</p>
{/block}
{/foreach}
Se você renderizar este template, o resultado será exatamente o mesmo com ou sem as tags {block}
. Os blocos
têm acesso a variáveis de escopos externos. Eles apenas dão a possibilidade de serem substituídos por um template filho:
{layout 'parent.Latte'}
{block post}
<article>
<header>{$post->title}</header>
<section>{$post->text}</section>
</article>
{/block}
Agora, ao renderizar o template filho, o loop usará o bloco definido no template filho child.Latte
em vez do
bloco definido em parent.Latte
; o template executado é então equivalente ao seguinte:
{foreach $posts as $post}
<article>
<header>{$post->title}</header>
<section>{$post->text}</section>
</article>
{/foreach}
No entanto, se criarmos uma nova variável dentro de um bloco nomeado ou substituirmos o valor de uma existente, a alteração será visível apenas dentro do bloco:
{var $foo = 'foo'}
{block post}
{do $foo = 'new value'}
{var $bar = 'bar'}
{/block}
foo: {$foo} // exibe: foo
bar: {$bar ?? 'not defined'} // exibe: not defined
O conteúdo do bloco pode ser modificado usando filtros. O exemplo a seguir remove todo o HTML e altera o tamanho das letras:
<title>{block title|stripHtml|capitalize}...{/block}</title>
A tag também pode ser escrita como um n:atributo:
<article n:block=post>
...
</article>
Blocos Locais
Cada bloco substitui o conteúdo do bloco pai com o mesmo nome – exceto os blocos locais. Em classes, seria algo como métodos privados. Assim, você pode criar o template sem se preocupar que, devido à coincidência de nomes de blocos, eles sejam substituídos por outro template.
{block local helper}
...
{/block}
Renderização de Blocos {include}
Veja também {include file}
Para exibir um bloco em um local específico, use a tag {include blockname}
:
<title>{block title}{/block}</title>
<h1>{include title}</h1>
Também é possível exibir um bloco de outro template:
{include footer from 'main.latte'}
O bloco renderizado não tem acesso às variáveis do contexto ativo, exceto quando o bloco é definido no mesmo arquivo onde é incluído. No entanto, ele tem acesso às variáveis globais.
Você pode passar variáveis para o bloco desta forma:
{include footer, foo: bar, id: 123}
Como nome do bloco, pode-se usar uma variável ou qualquer expressão PHP. Nesse caso, adicionamos a palavra-chave
block
antes da variável, para que o Latte saiba em tempo de compilação que se trata de um bloco, e não de uma inclusão de template, cujo nome também poderia estar em uma
variável:
{var $name = footer}
{include block $name}
O bloco também pode ser renderizado dentro de si mesmo, o que é útil, por exemplo, ao renderizar uma estrutura em árvore:
{define menu, $items}
<ul>
{foreach $items as $item}
<li>
{if is_array($item)}
{include menu, $item}
{else}
{$item}
{/if}
</li>
{/foreach}
</ul>
{/define}
Em vez de {include menu, ...}
, podemos então escrever {include this, ...}
, onde this
significa o bloco atual.
O bloco renderizado pode ser modificado usando filtros. O exemplo a seguir remove todo o HTML e altera o tamanho das letras:
<title>{include heading|stripHtml|capitalize}</title>
Bloco Pai
Se você precisar exibir o conteúdo de um bloco do template pai, use {include parent}
. Isso é útil se você
quiser apenas complementar o conteúdo do bloco pai em vez de substituí-lo completamente.
{block footer}
{include parent}
<a href="https://github.com/nette">GitHub</a>
<a href="https://twitter.com/nettefw">Twitter</a>
{/block}
Definições {define}
Além dos blocos, existem também “definições” no Latte. Em linguagens de programação comuns, elas seriam comparadas a funções. São úteis para reutilizar fragmentos de template para não se repetir.
O Latte tenta simplificar as coisas, então, basicamente, as definições são iguais aos blocos e tudo o que foi dito sobre blocos também se aplica às definições. Elas diferem dos blocos porque:
- estão contidas nas tags
{define}
- são renderizadas apenas quando incluídas via
{include}
- podem ter parâmetros definidos de forma semelhante às funções em PHP
{block foo}<p>Hello</p>{/block}
{* exibe: <p>Hello</p> *}
{define bar}<p>World</p>{/define}
{* não exibe nada *}
{include bar}
{* exibe: <p>World</p> *}
Imagine que você tem um template auxiliar com uma coleção de definições sobre como desenhar formulários HTML.
{define input, $name, $value, $type = 'text'}
<input type={$type} name={$name} value={$value}>
{/define}
{define textarea, $name, $value}
<textarea name={$name}>{$value}</textarea>
{/define}
Os argumentos são sempre opcionais com um valor padrão null
, a menos que um valor padrão seja especificado
(aqui 'text'
é o valor padrão para $type
). Também podem ser declarados tipos de parâmetros:
{define input, string $name, ...}
.
Carregamos o template com definições usando {import}
. As próprias
definições são renderizadas da mesma forma que os blocos:
<p>{include input, 'password', null, 'password'}</p>
<p>{include textarea, 'comment'}</p>
As definições não têm acesso às variáveis do contexto ativo, mas têm acesso às variáveis globais.
Nomes de Blocos Dinâmicos
O Latte permite grande flexibilidade na definição de blocos, pois o nome do bloco pode ser qualquer expressão PHP. Este
exemplo define três blocos com os nomes hi-Peter
, hi-John
e hi-Mary
:
{foreach [Peter, John, Mary] as $name}
{block "hi-$name"}Hi, I am {$name}.{/block}
{/foreach}
No template filho, podemos então redefinir, por exemplo, apenas um bloco:
{block hi-John}Hello. I am {$name}.{/block}
Assim, a saída ficará assim:
Hi, I am Peter.
Hello. I am John.
Hi, I am Mary.
Verificando a Existência de Blocos {ifset}
Veja também {ifset $var}
Usando o teste {ifset blockname}
, verificamos se um bloco (ou vários blocos) existe no contexto atual:
{ifset footer}
...
{/ifset}
{ifset footer, header, main}
...
{/ifset}
Como nome do bloco, pode-se usar uma variável ou qualquer expressão PHP. Nesse caso, adicionamos a palavra-chave
block
antes da variável, para deixar claro que não se trata de um teste de existência de variáveis:
{ifset block $name}
...
{/ifset}
A existência de blocos também é verificada pela função hasBlock()
:
{if hasBlock(header) || hasBlock(footer)}
...
{/if}
Dicas
Algumas dicas para trabalhar com blocos:
- O último bloco de nível superior não precisa ter uma tag de fechamento (o bloco termina no final do documento). Isso simplifica a escrita de templates filhos que contêm um bloco principal.
- Para melhor legibilidade, você pode indicar o nome do bloco na tag
{/block}
, por exemplo,{/block footer}
. No entanto, o nome deve corresponder ao nome do bloco. Em templates maiores, essa técnica ajuda a identificar quais tags de bloco estão sendo fechadas. - Você não pode definir diretamente várias tags de bloco com o mesmo nome no mesmo template. No entanto, isso pode ser alcançado usando nomes de blocos dinâmicos.
- Você pode usar n:atributos para definir blocos
como
<h1 n:block=title>Welcome to my awesome homepage</h1>
- Blocos também podem ser usados sem nomes apenas para aplicar filtros:
{block|strip} hello {/block}
Reutilização Horizontal {import}
A reutilização horizontal é o terceiro mecanismo no Latte para reutilização e herança. Permite carregar blocos de
outros templates. É semelhante a quando criamos um arquivo com funções auxiliares em PHP e depois o carregamos usando
require
.
Embora a herança de layout de template seja uma das características mais poderosas do Latte, ela é limitada à herança simples – um template pode estender apenas um outro template. A reutilização horizontal é uma maneira de alcançar herança múltipla.
Vamos ter um arquivo com definições de blocos:
{block sidebar}...{/block}
{block menu}...{/block}
Usando o comando {import}
, importamos todos os blocos e definições definidos em
blocks.latte
para outro template:
{import 'blocks.latte'}
{* agora os blocos sidebar e menu podem ser usados *}
Se você importar blocos no template pai (ou seja, usar {import}
em layout.latte
), os blocos também
estarão disponíveis em todos os templates filhos, o que é muito prático.
O template destinado à importação (por exemplo, blocks.latte
) não deve estender outro template, ou seja, usar {layout}
. No entanto, ele pode importar
outros templates.
A tag {import}
deve ser a primeira tag do template após {layout}
. O nome do template pode ser
qualquer expressão PHP:
{import $ajax ? 'ajax.latte' : 'not-ajax.latte'}
Você pode usar quantos comandos {import}
quiser no template. Se dois templates importados definirem o mesmo
bloco, o primeiro vence. No entanto, o template principal tem a prioridade mais alta e pode substituir qualquer bloco
importado.
O conteúdo dos blocos substituídos pode ser preservado incluindo o bloco da mesma forma que o bloco pai é incluído:
{layout 'layout.latte'}
{import 'blocks.latte'}
{block sidebar}
{include parent}
{/block}
{block title}...{/block}
{block content}...{/block}
Neste exemplo, {include parent}
chama o bloco sidebar
do template blocks.latte
.
Herança de Unidade {embed}
A herança de unidade estende a ideia da herança de layout ao nível de fragmentos de conteúdo. Enquanto a herança de layout trabalha com o “esqueleto do documento”, que é animado pelos templates filhos, a herança de unidade permite criar esqueletos para unidades menores de conteúdo e reutilizá-los onde quiser.
Na herança de unidade, a chave é a tag {embed}
. Ela combina o comportamento de {include}
e
{layout}
. Permite incluir o conteúdo de outro template ou bloco e, opcionalmente, passar variáveis, assim como no
caso de {include}
. Também permite substituir qualquer bloco definido dentro do template incluído, como ao usar
{layout}
.
Por exemplo, usaremos um elemento acordeão. Vejamos o esqueleto do elemento armazenado no template
collapsible.latte
:
<section class="collapsible {$modifierClass}">
<h4 class="collapsible__title">
{block title}{/block}
</h4>
<div class="collapsible__content">
{block content}{/block}
</div>
</section>
As tags {block}
definem dois blocos que os templates filhos podem preencher. Sim, como no caso do template pai na
herança de layout. Você também vê a variável $modifierClass
.
Vamos usar nosso elemento no template. É aqui que entra {embed}
. É uma tag extremamente poderosa que nos permite
fazer tudo: incluir o conteúdo do template do elemento, adicionar variáveis a ele e adicionar blocos com nosso
próprio HTML:
{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}
A saída pode parecer com isto:
<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>
Blocos dentro de tags incorporadas formam uma camada separada, independente de outros blocos. Portanto, eles podem ter o mesmo
nome que um bloco fora da incorporação e não são afetados de forma alguma. Usando a tag include dentro das tags {embed}
, você pode incluir blocos criados aqui, blocos do
template incorporado (que não são locais) e também blocos do template principal que,
por outro lado, são locais. Você também pode importar blocos de outros
arquivos:
{block outer}…{/block}
{block local hello}…{/block}
{embed 'collapsible.latte', modifierClass: my-style}
{import 'blocks.latte'}
{block inner}…{/block}
{block title}
{include inner} {* funciona, bloco definido dentro do embed *}
{include hello} {* funciona, bloco é local neste template *}
{include content} {* funciona, bloco definido no template incorporado *}
{include aBlockDefinedInImportedTemplate} {* funciona *}
{include outer} {* não funciona! - bloco está na camada externa *}
{/block}
{/embed}
Templates incorporados não têm acesso às variáveis do contexto ativo, mas têm acesso às variáveis globais.
Com {embed}
, é possível incluir não apenas templates, mas também outros blocos, e assim o exemplo anterior
poderia ser escrito desta forma:
{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}
Se passarmos uma expressão para {embed}
e não estiver claro se é o nome de um bloco ou de um arquivo,
adicionamos a palavra-chave block
ou file
:
{embed block $name} ... {/embed}
Casos de Uso
No Latte, existem diferentes tipos de herança e reutilização de código. Vamos resumir os principais conceitos para maior clareza:
{include template}
Caso de uso: Usar header.latte
e footer.latte
dentro de 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}
Caso de uso: Estender layout.latte
dentro de homepage.latte
e 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}
Caso de uso: sidebar.latte
em single.product.latte
e 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}
Caso de uso: Funções que recebem variáveis e renderizam algo.
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}
Caso de uso: Incluir pagination.latte
em product.table.latte
e
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}