Herança e Reutilização de Modelos
Os mecanismos de reutilização e herança dos modelos estão aqui para aumentar sua produtividade porque cada modelo contém apenas seu conteúdo único e os elementos e estruturas repetidos são reutilizados. Introduzimos três conceitos: herança de layout, reutilização horizontal e herança de unidade.
O conceito de herança de modelos Latte é semelhante ao de herança de classe PHP. Você define um **modelo pai*** que outros **modelos filhos*** podem estender e podem sobrepor-se a partes do modelo pai. Funciona muito bem quando os elementos compartilham uma estrutura comum. Parece complicado? Não se preocupe, não é.
Herança do Layout {layout}
Vamos analisar a herança do modelo de layout começando com um exemplo. Este é um template pai que chamaremos por exemplo
layout.latte
e ele define um documento de esqueleto 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 etiquetas {block}
definem três blocos que os modelos infantis podem preencher. Tudo o que a tag do bloco faz
é dizer ao motor do modelo que um modelo infantil pode substituir essas partes do modelo, definindo seu próprio bloco com
o mesmo nome.
Um modelo infantil pode ser parecido com este:
{layout 'layout.latte'}
{block title}My amazing blog{/block}
{block content}
<p>Welcome to my awesome homepage.</p>
{/block}
A tag {layout}
é a chave aqui. Ela diz ao motor do modelo que este modelo “estende” outro modelo. Quando
Latte renderiza este modelo, primeiro ele localiza o modelo pai – neste caso, layout.latte
.
Nesse momento, o motor do modelo notará as três etiquetas de blocos em layout.latte
e substituirá esses blocos
pelo conteúdo do modelo infantil. Note que, como o modelo criança não definiu o bloco página, o conteúdo do modelo
pai é usado em seu lugar. O conteúdo dentro de uma tag {block}
em um modelo pai é sempre usado como um
recurso.
O resultado pode ser parecido:
<!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>
Em um modelo infantil, os blocos só podem ser localizados no nível superior ou dentro de outro bloco, ou seja
{block content}
<h1>{block title}Welcome to my awesome homepage{/block}</h1>
{/block}
Também será sempre criado um bloco, independentemente de a condição ao redor {if}
ser avaliada como verdadeira
ou falsa. Ao contrário do que você possa pensar, este modelo define um 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 ao invés disso o seguinte
{block head}
{if $condition}
<meta name="robots" content="noindex, follow">
{/if}
{/block}
Os dados fora de um bloco em um modelo infantil são executados antes que o modelo de layout seja apresentado, assim você
pode usá-lo para definir variáveis como {var $foo = bar}
e propagar dados para toda a cadeia de herança:
{layout 'layout.latte'}
{var $robots = noindex}
...
Herança Multilevel
Você pode usar tantos níveis de herança quantos forem necessários. Uma maneira comum de usar a herança de layout é a seguinte abordagem em três níveis:
- Crie um modelo
layout.latte
que contenha a aparência principal de seu site. - Crie um modelo
layout-SECTIONNAME.latte
para cada seção de seu site. Por exemplo,layout-news.latte
,layout-blog.latte
etc. Todos estes modelos estendemlayout.latte
e incluem estilos/design específicos de seção. - Crie modelos individuais para cada tipo de página, tais como um artigo de notícia ou entrada no blog. Estes modelos estendem o modelo apropriado da seção.
Hereditariedade de layout dinâmico
Você pode usar uma variável ou qualquer expressão PHP como o nome do modelo pai, assim a herança pode se comportar dinamicamente:
{layout $standalone ? 'minimum.latte' : 'layout.latte'}
Você também pode usar o Latte API para escolher o modelo de layout automaticamente.
Dicas
Aqui estão algumas dicas para trabalhar com a herança de layout:
- Se você usar
{layout}
em um template, ele deve ser a primeira etiqueta do template nesse template. - O layout pode ser pesquisado automaticamente
(como nos apresentadores). Nesse caso, se
o modelo não tiver um layout, ele indicará isso com a tag
{layout none}
. - A tag
{layout}
tem o pseudônimo{extends}
. - O nome do arquivo do modelo estendido depende do carregador de modelos.
- Você pode ter tantos blocos quantos quiser. Lembre-se, os modelos infantis não precisam definir todos os blocos dos pais, assim você pode preencher padrões razoáveis em um número de blocos, e depois definir apenas os que você precisa mais tarde.
Blocos {block}
Veja também anônimo {block}
Um bloco fornece uma maneira de mudar a forma como uma determinada parte de um modelo é renderizada, mas não interfere de forma alguma com a lógica ao seu redor. Tomemos o seguinte exemplo para ilustrar como um bloco funciona e, mais importante, como ele não funciona:
{foreach $posts as $post}
{block post}
<h1>{$post->title}</h1>
<p>{$post->body}</p>
{/block}
{/foreach}
Se você renderizar este modelo, o resultado seria exatamente o mesmo com ou sem as etiquetas de bloco. Os blocos têm acesso a variáveis de escopos externos. É apenas uma forma de torná-lo anulável por um modelo infantil:
{layout 'parent.Latte'}
{block post}
<article>
<header>{$post->title}</header>
<section>{$post->text}</section>
</article>
{/block}
Agora, ao renderizar o modelo criança, o laço vai usar o bloco definido no modelo criança child.Latte
ao
invés do definido no modelo base parent.Latte
; o modelo executado é então equivalente ao seguinte:
{foreach $posts as $post}
<article>
<header>{$post->title}</header>
<section>{$post->text}</section>
</article>
{/foreach}
Entretanto, se criarmos uma nova variável dentro de um bloco nomeado ou substituirmos um valor de um já existente, a mudança será visível apenas dentro do bloco:
{var $foo = 'foo'}
{block post}
{do $foo = 'new value'}
{var $bar = 'bar'}
{/block}
foo: {$foo} // prints: foo
bar: {$bar ?? 'not defined'} // prints: not defined
O conteúdo do bloco pode ser modificado por filtros. O exemplo a seguir remove todo HTML e o título do bloco:
<title>{block title|stripHtml|capitalize}...{/block}</title>
A etiqueta também pode ser escrita como n:atributo:
<article n:block=post>
...
</article>
Blocos locais
Cada bloco substitui o conteúdo do bloco pai com o mesmo nome. Exceto para blocos locais. Eles são algo como métodos privados na classe. Você pode criar um modelo sem se preocupar que – devido à coincidência de nomes de blocos – eles seriam sobregravados por um segundo modelo.
{block local helper}
...
{/block}
Blocos de Impressão {include}
Veja também {include file}
Para imprimir um bloco em um lugar específico, use a tag {include blockname}
:
<title>{block title}{/block}</title>
<h1>{include title}</h1>
Você também pode exibir bloco a partir de outro modelo:
{include footer from 'main.latte'}
O bloco impresso não tem acesso às variáveis do contexto ativo, exceto se o bloco estiver definido no mesmo arquivo onde está incluído. No entanto, eles têm acesso às variáveis globais.
Você pode passar variáveis para o bloco da seguinte maneira:
{include footer, foo: bar, id: 123}
Você pode usar uma variável ou qualquer expressão em PHP como o nome do bloco. Neste caso, adicione a palavra-chave
block
antes da variável, para que se saiba em tempo de compilação que se trata de um bloco, e não insira um modelo, cujo nome também poderia estar na variável:
{var $name = footer}
{include block $name}
O bloco também pode ser impresso dentro dele 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, ...}
, também podemos escrever {include this, ...}
onde this
significa bloco atual.
O conteúdo impresso pode ser modificado por filtros. O exemplo a seguir remove todo o HTML e o título do arquivo:
<title>{include heading|stripHtml|capitalize}</title>
Bloco dos Pais
Se você precisar imprimir o conteúdo do bloco a partir do modelo pai, a declaração {include parent}
fará
o truque. Isto é útil se você quiser adicionar ao conteúdo de um 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, há também “definições” em Latte. Elas são comparáveis com funções em linguagens de programação regulares. Elas são úteis para reutilizar fragmentos de modelos para não se repetir.
O Latte tenta manter as coisas simples, portanto, basicamente, as definições são iguais aos blocos e tudo o que é dito sobre blocos também se aplica às definições. Elas diferem dos blocos no seguinte aspecto:
- são colocadas em tags
{define}
- são renderizadas somente quando são inseridas via
{include}
- você pode definir parâmetros para elas como funções no 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 que você tenha um modelo 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 de uma definição são sempre opcionais com valor padrão null
, a menos que o valor padrão seja
especificado (aqui 'text'
é o valor padrão para $type
). Os tipos de parâmetros também podem ser
declarados: {define input, string $name, ...}
.
O modelo com as definições é carregado 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 porque o nome do bloco pode ser qualquer expressão PHP. Este
exemplo define três blocos chamados hi-Peter
, hi-John
e hi-Mary
:
{foreach [Peter, John, Mary] as $name}
{block "hi-$name"}Hi, I am {$name}.{/block}
{/foreach}
Por exemplo, podemos redefinir apenas um bloco em um modelo infantil:
{block hi-John}Hello. I am {$name}.{/block}
Assim, a produção será parecida com esta:
Hi, I am Peter.
Hello. I am John.
Hi, I am Mary.
Verificação da existência do bloco {ifset}
Veja também {ifset $var}
Use o teste {ifset blockname}
para verificar se um bloco (ou mais blocos) existe no contexto atual:
{ifset footer}
...
{/ifset}
{ifset footer, header, main}
...
{/ifset}
Você pode usar uma variável ou qualquer expressão em PHP como o nome do bloco. Neste caso, adicione a palavra-chave
block
antes da variável para deixar claro que não é a variável que é verificada:
{ifset block $name}
...
{/ifset}
A existência de blocos também é retornada pela função hasBlock()
:
{if hasBlock(header) || hasBlock(footer)}
...
{/if}
Dicas
Aqui estão algumas dicas para trabalhar com blocos:
- O último bloco de nível superior não precisa ter etiqueta de fechamento (o bloco termina com o final do documento). Isto simplifica a escrita de modelos infantis, que é um bloco primário.
- Para uma legibilidade extra, você pode opcionalmente dar um nome à sua tag
{/block}
, por exemplo{/block footer}
. Entretanto, o nome deve corresponder ao nome do bloco. Em modelos maiores, esta técnica ajuda você a ver quais etiquetas de bloco estão sendo fechadas. - Você não pode definir diretamente várias etiquetas de bloco com o mesmo nome no mesmo modelo. Mas isto pode ser conseguido 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>
- Os blocos também podem ser usados sem nomes apenas para aplicar os filtros à saída:
{block|strip} hello {/block}
Reutilização Horizontal {import}
A reutilização horizontal é um terceiro mecanismo de reusabilidade e herança em Latte. Ele permite carregar blocos de outros modelos. É semelhante à criação de um arquivo PHP com funções de ajuda ou uma característica.
Embora a herança de layout de modelo seja um dos recursos mais avançados do Latte, ela é limitada à herança simples – um modelo só pode estender um outro modelo. A reutilização horizontal é uma forma de obter herança múltipla.
Vamos ter um conjunto de definições de blocos:
{block sidebar}...{/block}
{block menu}...{/block}
Usando o comando {import}
, importe todos os blocos e definições definidos em
blocks.latte
para outro modelo:
{import 'blocks.latte'}
{* Os blocos da barra lateral e do menu agora podem ser usados *}
Se você importar os blocos do modelo pai (ou seja, usar {import}
em layout.latte
), os blocos também
estarão disponíveis em todos os modelos filhos, o que é muito útil.
O modelo que se pretende importar (por exemplo, blocks.latte
) não deve estender outro modelo, ou seja, usar {layout}
. Entretanto, ele pode importar
outros modelos.
A tag {import}
deve ser a primeira tag modelo após {layout}
. O nome do template pode ser qualquer
expressão PHP:
{import $ajax ? 'ajax.latte' : 'not-ajax.latte'}
Você pode usar tantas declarações {import}
quantas quiser em qualquer modelo dado. Se dois gabaritos importados
definirem o mesmo bloco, o primeiro ganha. Entretanto, a maior prioridade é dada ao modelo principal, que pode sobrescrever
qualquer bloco importado.
Todos os blocos sobrepostos podem ser incluídos gradualmente, inserindo-os como bloco pai:
{layout 'layout.latte'}
{import 'blocks.latte'}
{block sidebar}
{include parent}
{/block}
{block title}...{/block}
{block content}...{/block}
Neste exemplo, {include parent}
chamará corretamente o bloco sidebar
a partir do modelo
blocks.latte
.
Herança da Unidade {embed}
A herança de unidade leva a idéia de herança de layout ao nível de fragmentos de conteúdo. Enquanto o layout inheritance funciona com “esqueletos de documentos”, que são trazidos à vida por modelos de crianças, a herança de unidade permite criar esqueletos para unidades menores de conteúdo e reutilizá-los onde você quiser.
Na unidade de herança, a chave é a tag {embed}
. Ela combina o comportamento de {include}
e
{layout}
. Ela permite incluir outro modelo ou conteúdo de bloco e, opcionalmente, passar variáveis, assim como
{include}
faz. Ela também permite que você substitua qualquer bloco definido dentro do modelo incluído, como o
{layout}
faz.
Por exemplo, vamos utilizar o elemento de acordeão dobrável. Vamos dar uma olhada no esqueleto do elemento no modelo
collapsible.latte
:
<section class="collapsible {$modifierClass}">
<h4 class="collapsible__title">
{block title}{/block}
</h4>
<div class="collapsible__content">
{block content}{/block}
</div>
</section>
As etiquetas {block}
definem dois blocos que os modelos infantis podem preencher. Sim, como no caso do modelo pai
no modelo de herança de layout. Você também pode ver a variável $modifierClass
.
Vamos usar nosso elemento no modelo. É aqui que entra {embed}
. É um kit super poderoso que nos permite fazer
todas as coisas: incluir o conteúdo do elemento no template, adicionar variáveis a ele e adicionar blocos com HTML
personalizado a ele:
{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}
O resultado pode ser parecido:
<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 etiquetas embutidas formam uma camada separada independente de outros blocos. Portanto, eles podem ter
o mesmo nome que o bloco fora do encaixe e não são afetados de forma alguma. Usando a tag inclua dentro de {embed}
tags você pode inserir blocos aqui criados, blocos de
modelo embutido (que * não são* locais), e também blocos de modelo principal que 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, o bloco é definido dentro do encaixe *}
{include hello} {* funciona, o bloco é local neste modelo *}
{include content} {* funciona, o bloco é definido no modelo embutido *}
{include aBlockDefinedInImportedTemplate} {* works *}
{include outer} {* does not work! - block is in outer layer *}
{/block}
{/embed}
Os modelos incorporados não têm acesso às variáveis do contexto ativo, mas eles têm acesso às variáveis globais.
Com {embed}
você pode inserir não apenas modelos, mas também outros blocos, de modo que 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 se trata de um bloco ou nome de arquivo,
acrescente a palavra-chave block
ou file
:
{embed block $name} ... {/embed}
Casos de uso
Há vários tipos de herança e reutilização de código em Latte. Vamos resumir os principais conceitos para uma maior liberação:
{include template}
Use Case: Usando header.latte
& 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}
**Uso caso***: Estendendo layout.latte
dentro de 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
em 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}
**Uso caso***: Uma função que obtém algumas variáveis e produz alguma marcação.
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}
**Uso caso***: Embutir pagination.latte
em 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}