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.
  • 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:

{* parent.Latte *}
{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:

{* child.Latte *}
{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 as variáveis desta forma:

{* desde Latte 2.9 *}
{include footer, foo: bar, id: 123}

{* antes do Latte 2.9 *}
{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.

Latte tenta fazer as coisas facilmente, então basicamente as definições são as mesmas dos blocos, e **tudo o que é dito sobre blocos também se aplica às definições***. Ele difere dos blocos apenas de três maneiras:

  1. eles podem aceitar argumentos
  2. eles não podem ter filtros
  3. elas são incluídas nas etiquetas {define} e o conteúdo dentro destas etiquetas não é enviado para saída até que você as inclua. Graças a isso, você pode criá-las em qualquer lugar:
{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 ter um modelo genérico de helper que define como renderizar formulários HTML através de definições:

{* forms.latte *}
{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, possível desde Latte 2.9.1). A partir do Latte 2.7, os tipos de parâmetros também podem ser declarados: {define input, string $name, ...}.

As definições não têm acesso às variáveis do contexto ativo, mas elas têm acesso às variáveis globais.

Eles estão incluídos da mesma forma que o bloco:

<p>{include input, 'password', null, 'password'}</p>
<p>{include textarea, 'comment'}</p>

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:

{* parent.latte *}
{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:

{* child.latte *}
{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}

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 modelos de layout seja uma das características mais poderosas do Latte, ela está limitada a uma única herança; um modelo só pode estender um outro modelo. Esta limitação torna a herança de modelos simples de entender e fácil de depurar:

{layout 'layout.latte'}

{block title}...{/block}
{block content}...{/block}

A reutilização horizontal é uma forma de alcançar o mesmo objetivo que a herança múltipla, mas sem a complexidade associada:

{layout 'layout.latte'}

{import 'blocks.latte'}

{block title}...{/block}
{block content}...{/block}

A declaração {import} diz à Latte para importar todos os blocos e definições definidas em blocks.latte para o modelo atual.

{* blocks.latte *}

{block sidebar}...{/block}

Neste exemplo, a declaração {import} importa o bloco sidebar para o modelo principal.

O modelo importado não deve estender outro modelo, e seu corpo deve estar vazio. Entretanto, o gabarito importado pode importar outros gabaritos.

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 'base.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}