Ereditarietà e riutilizzo dei template
I meccanismi di riutilizzo e di ereditarietà dei template aumenteranno la vostra produttività, poiché ogni template contiene solo il suo contenuto unico e gli elementi e le strutture ripetuti vengono riutilizzati. Introduciamo tre concetti: ereditarietà del layout, riutilizzo orizzontale e ereditarietà unitaria.
Il concetto di ereditarietà dei template di Latte è simile all'ereditarietà delle classi in PHP. Si definisce un template genitore da cui altri template figli possono ereditare e sovrascrivere parti del template genitore. Funziona alla grande quando gli elementi condividono una struttura comune. Sembra complicato? Non preoccupatevi, è molto semplice.
Ereditarietà del layout {layout}
Vediamo l'ereditarietà del template di layout, cioè del layout, con un esempio. Questo è un template genitore, che
chiameremo ad esempio layout.latte
, e che definisce lo scheletro di un 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>
I tag {block}
definiscono tre blocchi che i template figli possono riempire. Il tag block fa solo questo:
annuncia che questo posto può essere sovrascritto da un template figlio definendo il proprio blocco con lo stesso nome.
Un template figlio può assomigliare a questo:
{layout 'layout.latte'}
{block title}My amazing blog{/block}
{block content}
<p>Welcome to my awesome homepage.</p>
{/block}
La chiave qui è il tag {layout}
. Dice a Latte che questo template “estende” un altro template. Quando Latte
renderizza questo template, prima trova il template genitore – in questo caso layout.latte
.
A questo punto, Latte nota i tre tag block in layout.latte
e sostituisce questi blocchi con il contenuto del
template figlio. Poiché il template figlio non ha definito un blocco footer, viene utilizzato invece il contenuto del
template genitore. Il contenuto all'interno del tag {block}
nel template genitore viene sempre utilizzato come
fallback.
L'output potrebbe assomigliare a questo:
<!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>
Nel template figlio, i blocchi possono essere posizionati solo al livello più alto o all'interno di un altro blocco, cioè:
{block content}
<h1>{block title}Welcome to my awesome homepage{/block}</h1>
{/block}
Inoltre, un blocco verrà sempre creato indipendentemente dal fatto che la condizione {if}
circostante sia
valutata come vera o falsa. Quindi, anche se non sembra, questo template definisce il blocco.
{if false}
{block head}
<meta name="robots" content="noindex, follow">
{/block}
{/if}
Se si desidera che l'output all'interno del blocco venga visualizzato condizionatamente, utilizzare invece quanto segue:
{block head}
{if $condition}
<meta name="robots" content="noindex, follow">
{/if}
{/block}
Lo spazio al di fuori dei blocchi nel template figlio viene eseguito prima del rendering del template di layout, quindi è
possibile utilizzarlo per definire variabili come {var $foo = bar}
e per propagare i dati lungo l'intera catena di
ereditarietà:
{layout 'layout.latte'}
{var $robots = noindex}
...
Ereditarietà multilivello
È possibile utilizzare tutti i livelli di ereditarietà necessari. Un modo comune per utilizzare l'ereditarietà del layout è il seguente approccio a tre livelli:
- Creare un template
layout.latte
che contenga lo scheletro principale dell'aspetto del sito. - Creare un template
layout-SECTIONNAME.latte
per ogni sezione del sito. Ad esempiolayout-news.latte
,layout-blog.latte
, ecc. Tutti questi template estendonolayout.latte
e includono stili e design specifici per le singole sezioni. - Creare template individuali per ogni tipo di pagina, ad esempio un articolo di giornale o un post di blog. Questi template estendono il template della sezione corrispondente.
Ereditarietà dinamica
Come nome del template genitore si può usare una variabile o qualsiasi espressione PHP, quindi l'ereditarietà può comportarsi dinamicamente:
{layout $standalone ? 'minimum.latte' : 'layout.latte'}
È anche possibile utilizzare l'API di Latte per selezionare automaticamente il template di layout.
Suggerimenti
Ecco alcuni suggerimenti per lavorare con l'ereditarietà del layout:
- Se si utilizza
{layout}
in un template, deve essere il primo tag del template in quel template. - Il layout può essere trovato
automaticamente (come ad esempio nei presenter). In tal caso, se il template non deve
avere un layout, lo annuncia con il tag
{layout none}
. - Il tag
{layout}
ha un alias{extends}
. - Il nome del file di layout dipende dal loader.
- È possibile avere quanti blocchi si desidera. Ricordate che i template figli non devono definire tutti i blocchi genitori, quindi potete riempire valori predefiniti ragionevoli in alcuni blocchi e poi definire solo quelli necessari in seguito.
Blocchi {block}
Vedi anche {block}
anonimo
Un blocco rappresenta un modo per modificare come viene renderizzata una certa parte del template, ma non interferisce in alcun modo con la logica circostante. Nell'esempio seguente, mostreremo come funziona un blocco, ma anche come non funziona:
{foreach $posts as $post}
{block post}
<h1>{$post->title}</h1>
<p>{$post->body}</p>
{/block}
{/foreach}
Se si renderizza questo template, il risultato sarà esattamente lo stesso con o senza i tag {block}
. I blocchi
hanno accesso alle variabili dagli ambiti esterni. Danno solo la possibilità di essere sovrascritti da un template figlio:
{layout 'parent.Latte'}
{block post}
<article>
<header>{$post->title}</header>
<section>{$post->text}</section>
</article>
{/block}
Ora, durante il rendering del template figlio, il ciclo utilizzerà il blocco definito nel template figlio
child.Latte
invece del blocco definito in parent.Latte
; il template eseguito è quindi equivalente al
seguente:
{foreach $posts as $post}
<article>
<header>{$post->title}</header>
<section>{$post->text}</section>
</article>
{/foreach}
Tuttavia, se creiamo una nuova variabile all'interno di un blocco nominato o sostituiamo il valore di una esistente, la modifica sarà visibile solo all'interno del blocco:
{var $foo = 'foo'}
{block post}
{do $foo = 'new value'}
{var $bar = 'bar'}
{/block}
foo: {$foo} // prints: foo
bar: {$bar ?? 'not defined'} // prints: not defined
Il contenuto del blocco può essere modificato utilizzando i filtri. L'esempio seguente rimuove tutto l'HTML e cambia le maiuscole/minuscole:
<title>{block title|stripHtml|capitalize}...{/block}</title>
Il tag può anche essere scritto come n:attributo:
<article n:block=post>
...
</article>
Blocchi locali
Ogni blocco sovrascrive il contenuto del blocco genitore con lo stesso nome – ad eccezione dei blocchi locali. Nelle classi, sarebbero qualcosa come metodi privati. In questo modo è possibile creare un template senza preoccuparsi che, a causa della corrispondenza dei nomi dei blocchi, vengano sovrascritti da un altro template.
{block local helper}
...
{/block}
Rendering dei blocchi {include}
Vedi anche {include file}
Per visualizzare un blocco in una posizione specifica, utilizzare il tag {include blockname}
:
<title>{block title}{/block}</title>
<h1>{include title}</h1>
È anche possibile visualizzare un blocco da un altro template:
{include footer from 'main.latte'}
Il blocco renderizzato non ha accesso alle variabili del contesto attivo, tranne nel caso in cui il blocco sia definito nello stesso file in cui è incluso. Tuttavia, ha accesso alle variabili globali.
È possibile passare variabili al blocco in questo modo:
{include footer, foo: bar, id: 123}
Come nome del blocco si può usare una variabile o qualsiasi espressione PHP. In tal caso, aggiungiamo la parola chiave
block
prima della variabile, in modo che Latte sappia già in fase di compilazione che si tratta di un blocco e non
di inclusione del template, il cui nome potrebbe anche essere in una
variabile:
{var $name = footer}
{include block $name}
Un blocco può essere renderizzato anche all'interno di sé stesso, il che è utile ad esempio per renderizzare una struttura ad albero:
{define menu, $items}
<ul>
{foreach $items as $item}
<li>
{if is_array($item)}
{include menu, $item}
{else}
{$item}
{/if}
</li>
{/foreach}
</ul>
{/define}
Invece di {include menu, ...}
possiamo quindi scrivere {include this, ...}
, dove this
significa il blocco corrente.
Il blocco renderizzato può essere modificato utilizzando i filtri. L'esempio seguente rimuove tutto l'HTML e cambia le maiuscole/minuscole:
<title>{include heading|stripHtml|capitalize}</title>
Blocco genitore
Se è necessario visualizzare il contenuto di un blocco dal template genitore, utilizzare {include parent}
. Questo
è utile se si desidera solo aggiungere contenuto al blocco genitore invece di sovrascriverlo completamente.
{block footer}
{include parent}
<a href="https://github.com/nette">GitHub</a>
<a href="https://twitter.com/nettefw">Twitter</a>
{/block}
Definizioni {define}
Oltre ai blocchi, in Latte esistono anche le “definizioni”. Nei linguaggi di programmazione comuni, le paragoneremmo alle funzioni. Sono utili per riutilizzare frammenti di template per non ripetersi.
Latte cerca di rendere le cose semplici, quindi fondamentalmente le definizioni sono uguali ai blocchi e tutto ciò che viene detto sui blocchi vale anche per le definizioni. Si differenziano dai blocchi in quanto:
- sono racchiuse nei tag
{define}
- vengono renderizzate solo quando le si include tramite
{include}
- è possibile definire parametri per loro, in modo simile alle funzioni in 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> *}
Immaginate di avere un template di aiuto con una raccolta di definizioni su come disegnare form 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}
Gli argomenti sono sempre opzionali con un valore predefinito null
, a meno che non venga specificato un valore
predefinito (qui 'text'
è il valore predefinito per $type
). È possibile dichiarare anche i tipi dei
parametri: {define input, string $name, ...}
.
Carichiamo il template con le definizioni utilizzando {import}
. Le
definizioni stesse vengono renderizzate allo stesso modo dei blocchi:
<p>{include input, 'password', null, 'password'}</p>
<p>{include textarea, 'comment'}</p>
Le definizioni non hanno accesso alle variabili del contesto attivo, ma hanno accesso alle variabili globali.
Nomi dinamici dei blocchi
Latte consente una grande flessibilità nella definizione dei blocchi, poiché il nome del blocco può essere qualsiasi
espressione PHP. Questo esempio definisce tre blocchi con i nomi hi-Peter
, hi-John
e
hi-Mary
:
{foreach [Peter, John, Mary] as $name}
{block "hi-$name"}Hi, I am {$name}.{/block}
{/foreach}
Nel template figlio possiamo quindi ridefinire, ad esempio, solo un blocco:
{block hi-John}Hello. I am {$name}.{/block}
Quindi l'output assomiglierà a questo:
Hi, I am Peter.
Hello. I am John.
Hi, I am Mary.
Controllo dell'esistenza dei blocchi {ifset}
Vedi anche {ifset $var}
Utilizzando il test {ifset blockname}
verifichiamo se nel contesto corrente esiste un blocco (o più
blocchi):
{ifset footer}
...
{/ifset}
{ifset footer, header, main}
...
{/ifset}
Come nome del blocco si può usare una variabile o qualsiasi espressione PHP. In tal caso, aggiungiamo la parola chiave
block
prima della variabile per chiarire che non si tratta di un test sull'esistenza di variabili:
{ifset block $name}
...
{/ifset}
L'esistenza dei blocchi viene verificata anche dalla funzione hasBlock()
:
{if hasBlock(header) || hasBlock(footer)}
...
{/if}
Suggerimenti
Alcuni suggerimenti per lavorare con i blocchi:
- L'ultimo blocco di livello superiore non deve avere un tag di chiusura (il blocco termina alla fine del documento). Questo semplifica la scrittura di template figli che contengono un blocco primario.
- Per una migliore leggibilità, è possibile specificare il nome del blocco nel tag
{/block}
, ad esempio{/block footer}
. Tuttavia, il nome deve corrispondere al nome del blocco. Nei template più grandi, questa tecnica vi aiuterà a vedere quali tag di blocco vengono chiusi. - Non è possibile definire direttamente più tag di blocco con lo stesso nome nello stesso template. Tuttavia, questo può essere ottenuto utilizzando nomi dinamici dei blocchi.
- È possibile utilizzare n:attributi per definire blocchi
come
<h1 n:block=title>Welcome to my awesome homepage</h1>
- I blocchi possono essere utilizzati anche senza nomi solo per applicare filtri:
{block|strip} hello {/block}
Riutilizzo orizzontale {import}
Il riutilizzo orizzontale è il terzo meccanismo in Latte per il riutilizzo e l'ereditarietà. Consente di caricare blocchi da
altri template. È simile a quando in PHP creiamo un file con funzioni di aiuto che poi carichiamo usando
require
.
Sebbene l'ereditarietà del layout del template sia una delle funzionalità più potenti di Latte, è limitata all'ereditarietà semplice: un template può estendere solo un altro template. Il riutilizzo orizzontale è un modo per ottenere l'ereditarietà multipla.
Abbiamo un file con definizioni di blocchi:
{block sidebar}...{/block}
{block menu}...{/block}
Utilizzando il comando {import}
, importiamo tutti i blocchi e le definizioni definite
in blocks.latte
in un altro template:
{import 'blocks.latte'}
{* ora è possibile utilizzare i blocchi sidebar e menu *}
Se si importano i blocchi nel template genitore (cioè si usa {import}
in layout.latte
), i blocchi
saranno disponibili anche in tutti i template figli, il che è molto pratico.
Il template destinato all'importazione (ad es. blocks.latte
) non deve estendere un altro template, cioè usare {layout}
. Tuttavia, può importare altri
template.
Il tag {import}
dovrebbe essere il primo tag del template dopo {layout}
. Il nome del template può
essere qualsiasi espressione PHP:
{import $ajax ? 'ajax.latte' : 'not-ajax.latte'}
È possibile utilizzare quanti comandi {import}
si desidera nel template. Se due template importati definiscono lo
stesso blocco, vince il primo. Tuttavia, la priorità più alta spetta al template principale, che può sovrascrivere qualsiasi
blocco importato.
Il contenuto dei blocchi sovrascritti può essere preservato includendo il blocco nello stesso modo in cui viene incluso il blocco genitore:
{layout 'layout.latte'}
{import 'blocks.latte'}
{block sidebar}
{include parent}
{/block}
{block title}...{/block}
{block content}...{/block}
In questo esempio, {include parent}
chiama il blocco sidebar
dal template
blocks.latte
.
Ereditarietà unitaria {embed}
L'ereditarietà unitaria estende l'idea dell'ereditarietà del layout al livello dei frammenti di contenuto. Mentre l'ereditarietà del layout lavora con lo “scheletro del documento” che viene animato dai template figli, l'ereditarietà unitaria consente di creare scheletri per unità di contenuto più piccole e riutilizzarle ovunque si desideri.
Nell'ereditarietà unitaria, la chiave è il tag {embed}
. Combina il comportamento di {include}
e
{layout}
. Consente di incorporare il contenuto di un altro template o blocco e passare opzionalmente variabili,
proprio come nel caso di {include}
. Consente inoltre di sovrascrivere qualsiasi blocco definito all'interno del
template incorporato, come quando si utilizza {layout}
.
Ad esempio, utilizziamo un elemento accordion. Vediamo lo scheletro dell'elemento salvato nel template
collapsible.latte
:
<section class="collapsible {$modifierClass}">
<h4 class="collapsible__title">
{block title}{/block}
</h4>
<div class="collapsible__content">
{block content}{/block}
</div>
</section>
I tag {block}
definiscono due blocchi che i template figli possono riempire. Sì, come nel caso del template
genitore nell'ereditarietà del layout. Vedete anche la variabile $modifierClass
.
Utilizziamo il nostro elemento nel template. Qui entra in gioco {embed}
. È un tag estremamente potente che ci
consente di fare tutte queste cose: incorporare il contenuto del template dell'elemento, aggiungervi variabili e aggiungervi
blocchi con il nostro HTML personalizzato:
{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}
L'output potrebbe assomigliare a questo:
<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>
I blocchi all'interno dei tag incorporati formano un livello separato indipendente dagli altri blocchi. Pertanto, possono
avere lo stesso nome di un blocco al di fuori dell'incorporamento e non ne sono influenzati in alcun modo. Utilizzando il tag include all'interno dei tag {embed}
, è possibile includere i blocchi creati qui,
i blocchi dal template incorporato (che non sono locali) e anche i blocchi dal
template principale, che invece sono locali. È anche possibile importare
blocchi da altri file:
{block outer}…{/block}
{block local hello}…{/block}
{embed 'collapsible.latte', modifierClass: my-style}
{import 'blocks.latte'}
{block inner}…{/block}
{block title}
{include inner} {* works, block is defined inside embed *}
{include hello} {* works, block is local in this template *}
{include content} {* works, block is defined in embedded template *}
{include aBlockDefinedInImportedTemplate} {* works *}
{include outer} {* does not work! - block is in outer layer *}
{/block}
{/embed}
I template incorporati non hanno accesso alle variabili del contesto attivo, ma hanno accesso alle variabili globali.
Utilizzando {embed}
è possibile includere non solo template, ma anche altri blocchi, e quindi l'esempio
precedente potrebbe essere scritto in questo modo:
{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 passiamo un'espressione a {embed}
e non è chiaro se si tratti del nome di un blocco o di un file, aggiungiamo
la parola chiave block
o file
:
{embed block $name} ... {/embed}
Casi d'uso
In Latte esistono diversi tipi di ereditarietà e riutilizzo del codice. Riassumiamo i concetti principali per una maggiore chiarezza:
{include template}
Caso d'uso: Utilizzo di header.latte
e footer.latte
all'interno di
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 d'uso: Estensione di layout.latte
all'interno di 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 d'uso: sidebar.latte
in 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 d'uso: Funzioni a cui passiamo variabili e che renderizzano qualcosa.
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 d'uso: Inserimento di pagination.latte
in 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}