Herencia y reutilización de plantillas
Los mecanismos de reutilización y herencia de plantillas aumentarán su productividad, ya que cada plantilla contiene sólo su contenido único y los elementos y estructuras repetidos se reutilizan. Presentamos tres conceptos: herencia de layout, reutilización horizontal y herencia de unidades.
El concepto de herencia de plantillas Latte es similar a la herencia de clases en PHP. Se define una plantilla padre de la que otras plantillas hijas pueden heredar y pueden sobrescribir partes de la plantilla padre. Funciona muy bien cuando los elementos comparten una estructura común. ¿Suena complicado? No se preocupe, es muy fácil.
Herencia de layout {layout}
Veamos la herencia de la plantilla de diseño, es decir, el layout, con un ejemplo. Esta es la plantilla padre, que llamaremos,
por ejemplo, layout.latte
, y que define el esqueleto del 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>
Las etiquetas {block}
definen tres bloques que las plantillas hijas pueden rellenar. La etiqueta block simplemente
anuncia que este lugar puede ser sobrescrito por una plantilla hija definiendo su propio bloque con el mismo nombre.
Una plantilla hija puede verse así:
{layout 'layout.latte'}
{block title}Mi increíble blog{/block}
{block content}
<p>Bienvenido a mi impresionante página de inicio.</p>
{/block}
La clave aquí es la etiqueta {layout}
. Le dice a Latte que esta plantilla “extiende” otra plantilla. Cuando
Latte renderiza esta plantilla, primero encuentra la plantilla padre – en este caso layout.latte
.
En este punto, Latte nota las tres etiquetas de bloque en layout.latte
y reemplaza estos bloques con el contenido
de la plantilla hija. Dado que la plantilla hija no definió el bloque footer, se utiliza en su lugar el contenido de la
plantilla padre. El contenido dentro de la etiqueta {block}
en la plantilla padre siempre se utiliza como
respaldo.
La salida puede verse así:
<!doctype html>
<html lang="en">
<head>
<title>Mi increíble blog</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="content">
<p>Bienvenido a mi impresionante página de inicio.</p>
</div>
<div id="footer">
© Copyright 2008
</div>
</body>
</html>
En la plantilla hija, los bloques solo pueden colocarse en el nivel superior o dentro de otro bloque, es decir:
{block content}
<h1>{block title}Bienvenido a mi impresionante página de inicio{/block}</h1>
{/block}
Además, siempre se creará un bloque independientemente de si la condición {if}
circundante se evalúa como
verdadera o falsa. Así que, aunque no lo parezca, esta plantilla definirá el bloque.
{if false}
{block head}
<meta name="robots" content="noindex, follow">
{/block}
{/if}
Si desea que la salida dentro del bloque se muestre condicionalmente, use lo siguiente en su lugar:
{block head}
{if $condition}
<meta name="robots" content="noindex, follow">
{/if}
{/block}
El espacio fuera de los bloques en la plantilla hija se ejecuta antes de renderizar la plantilla de layout, por lo que puede
usarlo para definir variables como {var $foo = bar}
y propagar datos a lo largo de toda la cadena de herencia:
{layout 'layout.latte'}
{var $robots = noindex}
...
Herencia multinivel
Puede usar tantos niveles de herencia como necesite. Una forma común de usar la herencia de layout es el siguiente enfoque de tres niveles:
- Cree una plantilla
layout.latte
que contenga el esqueleto principal de la apariencia del sitio web. - Cree una plantilla
layout-SECTIONNAME.latte
para cada sección de su sitio web. Por ejemplo,layout-news.latte
,layout-blog.latte
, etc. Todas estas plantillas extiendenlayout.latte
e incluyen estilos y diseño específicos para cada sección. - Cree plantillas individuales para cada tipo de página, como un artículo de noticias o una entrada de blog. Estas plantillas extienden la plantilla de sección correspondiente.
Herencia dinámica
Como nombre de la plantilla padre se puede usar una variable o cualquier expresión PHP, por lo que la herencia puede comportarse dinámicamente:
{layout $standalone ? 'minimum.latte' : 'layout.latte'}
También puede usar la API de Latte para seleccionar automáticamente la plantilla de layout.
Consejos
Aquí hay algunos consejos para trabajar con la herencia de layout:
- Si usa
{layout}
en una plantilla, debe ser la primera etiqueta de plantilla en esa plantilla. - El layout se puede buscar
automáticamente (como por ejemplo en los presenters). En tal caso, si la plantilla no debe
tener layout, lo indica con la etiqueta
{layout none}
. - La etiqueta
{layout}
tiene un alias{extends}
. - El nombre del archivo de layout depende del loader.
- Puede tener tantos bloques como quiera. Recuerde que las plantillas hijas no necesitan definir todos los bloques padres, por lo que puede rellenar valores por defecto razonables en varios bloques y luego definir solo los que necesite más tarde.
Bloques {block}
Vea también el anónimo {block}
Un bloque representa una forma de cambiar cómo se renderiza una parte específica de una plantilla, pero no interfiere de ninguna manera con la lógica que lo rodea. En el siguiente ejemplo, mostraremos cómo funciona un bloque, pero también cómo no funciona:
{foreach $posts as $post}
{block post}
<h1>{$post->title}</h1>
<p>{$post->body}</p>
{/block}
{/foreach}
Si renderiza esta plantilla, el resultado será exactamente el mismo con o sin las etiquetas {block}
. Los bloques
tienen acceso a las variables de los ámbitos externos. Simplemente dan la posibilidad de ser sobrescritos por una
plantilla hija:
{layout 'parent.Latte'}
{block post}
<article>
<header>{$post->title}</header>
<section>{$post->text}</section>
</article>
{/block}
Ahora, al renderizar la plantilla hija, el bucle usará el bloque definido en la plantilla hija child.Latte
en
lugar del bloque definido en parent.Latte
; la plantilla ejecutada es entonces equivalente a la siguiente:
{foreach $posts as $post}
<article>
<header>{$post->title}</header>
<section>{$post->text}</section>
</article>
{/foreach}
Sin embargo, si creamos una nueva variable dentro de un bloque con nombre o reemplazamos el valor de una existente, el cambio solo será visible dentro del bloque:
{var $foo = 'foo'}
{block post}
{do $foo = 'new value'}
{var $bar = 'bar'}
{/block}
foo: {$foo} // imprime: foo
bar: {$bar ?? 'not defined'} // imprime: not defined
El contenido del bloque se puede modificar usando filtros. El siguiente ejemplo elimina todo el HTML y cambia las mayúsculas y minúsculas:
<title>{block title|stripHtml|capitalize}...{/block}</title>
La etiqueta también se puede escribir como un n:atributo:
<article n:block=post>
...
</article>
Bloques locales
Cada bloque sobrescribe el contenido del bloque padre con el mismo nombre – excepto los bloques locales. En las clases, esto sería algo así como métodos privados. De esta manera, puede crear la plantilla sin preocuparse de que, debido a la coincidencia de nombres de bloques, sean sobrescritos desde otra plantilla.
{block local helper}
...
{/block}
Renderizado de bloques {include}
Vea también {include file}
Para mostrar un bloque en un lugar específico, use la etiqueta {include blockname}
:
<title>{block title}{/block}</title>
<h1>{include title}</h1>
También se puede mostrar un bloque de otra plantilla:
{include footer from 'main.latte'}
El bloque renderizado no tiene acceso a las variables del contexto activo, excepto cuando el bloque se define en el mismo archivo donde se incluye. Sin embargo, tiene acceso a las variables globales.
Puede pasar variables al bloque de esta manera:
{include footer, foo: bar, id: 123}
Como nombre del bloque se puede usar una variable o cualquier expresión en PHP. En tal caso, antes de la variable agregamos
la palabra clave block
, para que Latte sepa en tiempo de compilación que se trata de un bloque y no de una inclusión de plantillas, cuyo nombre también podría estar en una
variable:
{var $name = footer}
{include block $name}
Un bloque también se puede renderizar dentro de sí mismo, lo cual es útil, por ejemplo, al renderizar una estructura de árbol:
{define menu, $items}
<ul>
{foreach $items as $item}
<li>
{if is_array($item)}
{include menu, $item}
{else}
{$item}
{/if}
</li>
{/foreach}
</ul>
{/define}
En lugar de {include menu, ...}
, podemos escribir {include this, ...}
, donde this
significa el bloque actual.
El bloque renderizado se puede modificar usando filtros. El siguiente ejemplo elimina todo el HTML y cambia las mayúsculas y minúsculas:
<title>{include heading|stripHtml|capitalize}</title>
Bloque padre
Si necesita mostrar el contenido del bloque de la plantilla padre, use {include parent}
. Esto es útil si solo
desea complementar el contenido del bloque padre en lugar de sobrescribirlo por completo.
{block footer}
{include parent}
<a href="https://github.com/nette">GitHub</a>
<a href="https://twitter.com/nettefw">Twitter</a>
{/block}
Definiciones {define}
Además de los bloques, en Latte también existen las “definiciones”. En los lenguajes de programación comunes, las compararíamos con funciones. Son útiles para reutilizar fragmentos de plantilla para no repetirse.
Latte intenta hacer las cosas simples, por lo que básicamente las definiciones son lo mismo que los bloques y todo lo que se dice sobre los bloques también se aplica a las definiciones. Se diferencian de los bloques en que:
- están encerradas en etiquetas
{define}
- se renderizan teprve, až když je vložíte přes
{include}
- se les pueden definir parámetros de manera similar a las funciones en PHP
{block foo}<p>Hello</p>{/block}
{* imprime: <p>Hello</p> *}
{define bar}<p>World</p>{/define}
{* no imprime nada *}
{include bar}
{* imprime: <p>World</p> *}
Imagine que tiene una plantilla auxiliar con una colección de definiciones sobre cómo dibujar formularios 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}
Los argumentos siempre son opcionales con un valor por defecto de null
, a menos que se especifique un valor por
defecto (aquí 'text'
es el valor por defecto para $type
). También se pueden declarar los tipos de
parámetros: {define input, string $name, ...}
.
La plantilla con definiciones se carga usando {import}
. Las
definiciones en sí se renderizan de la misma manera que los bloques:
<p>{include input, 'password', null, 'password'}</p>
<p>{include textarea, 'comment'}</p>
Las definiciones no tienen acceso a las variables del contexto activo, pero tienen acceso a las variables globales.
Nombres de bloque dinámicos
Latte permite una gran flexibilidad al definir bloques, ya que el nombre del bloque puede ser cualquier expresión PHP. Este
ejemplo define tres bloques con los nombres hi-Peter
, hi-John
y hi-Mary
:
{foreach [Peter, John, Mary] as $name}
{block "hi-$name"}Hi, I am {$name}.{/block}
{/foreach}
En la plantilla hija, podemos redefinir, por ejemplo, solo un bloque:
{block hi-John}Hello. I am {$name}.{/block}
Así que la salida se verá así:
Hi, I am Peter.
Hello. I am John.
Hi, I am Mary.
Comprobación de existencia de bloques {ifset}
Vea también {ifset $var}
Usando la prueba {ifset blockname}
comprobamos si en el contexto actual existe un bloque (o varios bloques):
{ifset footer}
...
{/ifset}
{ifset footer, header, main}
...
{/ifset}
Como nombre del bloque se puede usar una variable o cualquier expresión en PHP. En tal caso, antes de la variable agregamos
la palabra clave block
, para que quede claro que no se trata de una prueba de existencia de variables:
{ifset block $name}
...
{/ifset}
La existencia de bloques también la verifica la función hasBlock()
:
{if hasBlock(header) || hasBlock(footer)}
...
{/if}
Consejos
Algunos consejos para trabajar con bloques:
- El último bloque de nivel superior no necesita tener una etiqueta de cierre (el bloque termina al final del documento). Esto simplifica la escritura de plantillas hijas que contienen un bloque principal.
- Para una mejor legibilidad, puede indicar el nombre del bloque en la etiqueta
{/block}
, por ejemplo{/block footer}
. Sin embargo, el nombre debe coincidir con el nombre del bloque. En plantillas más grandes, esta técnica le ayuda a ver qué etiquetas de bloque se están cerrando. - No puede definir directamente varias etiquetas de bloque con el mismo nombre en la misma plantilla. Sin embargo, esto se puede lograr usando nombres de bloque dinámicos.
- Puede usar n:atributos para definir bloques
como
<h1 n:block=title>Bienvenido a mi impresionante página de inicio</h1>
- Los bloques también se pueden usar sin nombres solo para aplicar filtros:
{block|strip} hello {/block}
Reutilización horizontal {import}
La reutilización horizontal es el tercer mecanismo en Latte para la reutilización y herencia. Permite cargar bloques de otras
plantillas. Es similar a cuando en PHP creamos un archivo con funciones auxiliares que luego cargamos usando
require
.
Aunque la herencia de layout de plantillas es una de las características más potentes de Latte, está limitada a la herencia simple – una plantilla solo puede extender otra plantilla. La reutilización horizontal es una forma de lograr la herencia múltiple.
Tengamos un archivo con definiciones de bloques:
{block sidebar}...{/block}
{block menu}...{/block}
Usando el comando {import}
importamos todos los bloques y definiciones definidas en
blocks.latte
a otra plantilla:
{import 'blocks.latte'}
{* ahora se pueden usar los bloques sidebar y menu *}
Si importa los bloques en la plantilla padre (es decir, usa {import}
en layout.latte
), los bloques
también estarán disponibles en todas las plantillas hijas, lo cual es muy práctico.
La plantilla destinada a ser importada (por ejemplo, blocks.latte
) no debe extender otra plantilla, es decir, usar {layout}
. Sin embargo, puede importar
otras plantillas.
La etiqueta {import}
debe ser la primera etiqueta de plantilla después de {layout}
. El nombre de la
plantilla puede ser cualquier expresión PHP:
{import $ajax ? 'ajax.latte' : 'not-ajax.latte'}
En la plantilla puede usar tantos comandos {import}
como desee. Si dos plantillas importadas definen el mismo
bloque, gana la primera. Sin embargo, la plantilla principal tiene la máxima prioridad y puede sobrescribir cualquier bloque
importado.
El contenido de los bloques sobrescritos se puede conservar incluyendo el bloque de la misma manera que se incluye el bloque padre:
{layout 'layout.latte'}
{import 'blocks.latte'}
{block sidebar}
{include parent}
{/block}
{block title}...{/block}
{block content}...{/block}
En este ejemplo, {include parent}
llama al bloque sidebar
de la plantilla
blocks.latte
.
Herencia de unidades {embed}
La herencia de unidades extiende la idea de la herencia de layout al nivel de fragmentos de contenido. Mientras que la herencia de layout funciona con el “esqueleto del documento”, que las plantillas hijas animan, la herencia de unidades le permite crear esqueletos para unidades de contenido más pequeñas y reutilizarlas donde quiera.
En la herencia de unidades, la clave es la etiqueta {embed}
. Combina el comportamiento de {include}
y
{layout}
. Permite incluir el contenido de otra plantilla o bloque y opcionalmente pasar variables, al igual que en
el caso de {include}
. También permite sobrescribir cualquier bloque definido dentro de la plantilla incluida, como
al usar {layout}
.
Por ejemplo, usemos un elemento acordeón. Veamos el esqueleto del elemento almacenado en la plantilla
collapsible.latte
:
<section class="collapsible {$modifierClass}">
<h4 class="collapsible__title">
{block title}{/block}
</h4>
<div class="collapsible__content">
{block content}{/block}
</div>
</section>
Las etiquetas {block}
definen dos bloques que las plantillas hijas pueden rellenar. Sí, como en el caso de la
plantilla padre en la herencia de layout. También ve la variable $modifierClass
.
Usemos nuestro elemento en la plantilla. Aquí es donde entra en juego {embed}
. Es una etiqueta
extraordinariamente potente que nos permite hacer todas estas cosas: incluir el contenido de la plantilla del elemento, agregarle
variables y agregarle bloques con nuestro propio 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}
La salida puede verse así:
<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>
Los bloques dentro de las etiquetas incluidas forman una capa separada independiente de otros bloques. Por lo tanto, pueden
tener el mismo nombre que un bloque fuera de la inclusión y no se ven afectados de ninguna manera. Usando la etiqueta include dentro de las etiquetas {embed}
puede incluir bloques creados aquí, bloques
de la plantilla incluida (que no son locales) y también bloques de la plantilla
principal que, por el contrario, son locales. También puede importar bloques
de otros archivos:
{block outer}…{/block}
{block local hello}…{/block}
{embed 'collapsible.latte', modifierClass: my-style}
{import 'blocks.latte'}
{block inner}…{/block}
{block title}
{include inner} {* funciona, el bloque está definido dentro de embed *}
{include hello} {* funciona, el bloque es local en esta plantilla *}
{include content} {* funciona, el bloque está definido en la plantilla incluida *}
{include aBlockDefinedInImportedTemplate} {* funciona *}
{include outer} {* ¡no funciona! - el bloque está en la capa exterior *}
{/block}
{/embed}
Las plantillas incluidas no tienen acceso a las variables del contexto activo, pero tienen acceso a las variables globales.
Usando {embed}
se pueden incluir no solo plantillas, sino también otros bloques, por lo que el ejemplo anterior
podría escribirse de esta manera:
{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}
Si pasamos una expresión a {embed}
y no está claro si es el nombre de un bloque o un archivo, agregamos la
palabra clave block
o file
:
{embed block $name} ... {/embed}
Casos de uso
En Latte existen diferentes tipos de herencia y reutilización de código. Resumamos los conceptos principales para una mayor claridad:
{include template}
Caso de uso: Usar header.latte
y 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: Extender layout.latte
dentro de homepage.latte
y about.latte
.
layout.latte
{include 'header.latte'}
<main>{block main}{/block}</main>
{include 'footer.latte'}
homepage.latte
{layout 'layout.latte'}
{block main}
<p>Página de inicio</p>
{/block}
about.latte
{layout 'layout.latte'}
{block main}
<p>Página Acerca de</p>
{/block}
{import}
Caso de uso: sidebar.latte
en single.product.latte
y single.service.latte
.
sidebar.latte
{block sidebar}<aside>Esta es la barra lateral</aside>{/block}
single.product.latte
{layout 'product.layout.latte'}
{import 'sidebar.latte'}
{block main}<main>Página del producto</main>{/block}
single.service.latte
{layout 'service.layout.latte'}
{import 'sidebar.latte'}
{block main}<main>Página del servicio</main>{/block}
{define}
Caso de uso: Funciones a las que pasamos variables y renderizan 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
en product.table.latte
y
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}Primera página de productos{/block}
{block last}Última página de productos{/block}
{/embed}
service.table.latte
{embed 'pagination.latte', min: 1, max: $services->count}
{block first}Primera página de servicios{/block}
{block last}Última página de servicios{/block}
{/embed}