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}&copy; 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">
		&copy; 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:

  1. Crie um modelo layout.latte que contenha a aparência principal de seu site.
  2. 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 estendem layout.latte e incluem estilos/design específicos de seção.
  3. 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:

  1. são colocadas em tags {define}
  2. são renderizadas somente quando são inseridas via {include}
  3. 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}
versão: 3.0