Moștenirea și reutilizarea șabloanelor

Mecanismele de reutilizare și de moștenire a șabloanelor au rolul de a vă spori productivitatea, deoarece fiecare șablon conține doar conținutul său unic, iar elementele și structurile repetate sunt reutilizate. Prezentăm trei concepte: moștenirea aspectului, reutilizarea orizontală și moștenirea unitară.

Conceptul de moștenire a șabloanelor Latte este similar cu moștenirea claselor PHP. Definiți un șablon părinte pe care alte șabloane copii îl pot extinde și pot suprascrie părți ale șablonului părinte. Funcționează foarte bine atunci când elementele au o structură comună. Sună complicat? Nu vă faceți griji, nu este.

Moștenirea aspectului {layout}

Să analizăm moștenirea șablonului de aspect pornind de la un exemplu. Acesta este un șablon părinte pe care îl vom numi, de exemplu, layout.latte și definește un document HTML schelet.

<!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>

Etichetele {block} definesc trei blocuri pe care șabloanele copil le pot completa. Tot ceea ce face tag-ul block este să spună motorului de șabloane că un șablon copil poate înlocui aceste părți ale șablonului prin definirea propriului bloc cu același nume.

Un șablon copil ar putea arăta astfel:

{layout 'layout.latte'}

{block title}My amazing blog{/block}

{block content}
	<p>Welcome to my awesome homepage.</p>
{/block}

Eticheta {layout} este cheia aici. Aceasta indică motorului de șabloane că acest șablon “extinde” un alt șablon. Când Latte redă acest șablon, mai întâi localizează părintele – în acest caz, layout.latte.

În acel moment, motorul șablonului va observa cele trei etichete de bloc din layout.latte și va înlocui acele blocuri cu conținutul șablonului copil. Rețineți că, deoarece șablonul copil nu a definit blocul footer, se utilizează în schimb conținutul șablonului părinte. Conținutul din cadrul unei etichete {block} dintr-un șablon părinte este întotdeauna utilizat ca soluție de rezervă.

Rezultatul ar putea arăta astfel:

<!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>

Într-un șablon copil, blocurile pot fi amplasate fie la nivelul superior, fie în interiorul unui alt bloc, adică:

{block content}
	<h1>{block title}Welcome to my awesome homepage{/block}</h1>
{/block}

De asemenea, un bloc va fi creat întotdeauna în indiferent dacă condiția {if} din jur este evaluată ca fiind adevărată sau falsă. Contrar a ceea ce ați putea crede, acest șablon definește un bloc.

{if false}
	{block head}
		<meta name="robots" content="noindex, follow">
	{/block}
{/if}

Dacă doriți ca ieșirea din interiorul blocului să fie afișată condiționat, utilizați în schimb următoarele:

{block head}
	{if $condition}
		<meta name="robots" content="noindex, follow">
	{/if}
{/block}

Datele din afara unui bloc dintr-un șablon copil sunt executate înainte ca șablonul de prezentare să fie redat, astfel încât îl puteți utiliza pentru a defini variabile precum {var $foo = bar} și pentru a propaga datele în întregul lanț de moștenire:

{layout 'layout.latte'}
{var $robots = noindex}

...

Moștenirea pe mai multe niveluri

Puteți utiliza oricâte niveluri de moștenire sunt necesare. O modalitate obișnuită de utilizare a moștenirii aspectului este următoarea abordare pe trei niveluri:

  1. Creați un șablon layout.latte care deține aspectul principal al site-ului dumneavoastră.
  2. Creați un șablon layout-SECTIONNAME.latte pentru fiecare secțiune a site-ului dumneavoastră. De exemplu, layout-news.latte, layout-blog.latte etc. Toate aceste șabloane extind layout.latte și includ stiluri/design specifice fiecărei secțiuni.
  3. Creați șabloane individuale pentru fiecare tip de pagină, cum ar fi un articol de știri sau o intrare pe blog. Aceste șabloane extind șablonul de secțiune corespunzător.

Moștenirea dinamică a aspectului (Dynamic Layout Inheritance)

Puteți utiliza o variabilă sau orice expresie PHP ca nume al șablonului părinte, astfel încât moștenirea să se poată comporta dinamic:

{layout $standalone ? 'minimum.latte' : 'layout.latte'}

De asemenea, puteți utiliza Latte API pentru a alege automat șablonul de aspect.

Sfaturi

Iată câteva sfaturi pentru a lucra cu moștenirea aspectului:

  • Dacă utilizați {layout} într-un șablon, trebuie să fie prima etichetă de șablon din șablonul respectiv.
  • Layout-ul poate fi căutat în mod automat (la fel ca în prezentări). În acest caz, dacă șablonul nu ar trebui să aibă un layout, se va indica acest lucru cu eticheta {layout none}.
  • Eticheta {layout} are aliasul {extends}.
  • Numele de fișier al șablonului extins depinde de încărcătorul de șabloane.
  • Puteți avea oricâte blocuri doriți. Nu uitați că șabloanele copil nu trebuie să definească toate blocurile părinte, astfel încât puteți completa valori implicite rezonabile într-un număr de blocuri, apoi să le definiți doar pe cele de care aveți nevoie mai târziu.

Blocuri {block}

A se vedea, de asemenea, anonim {block}

Un bloc oferă o modalitate de a schimba modul în care este redată o anumită parte a unui șablon, dar nu interferează în niciun fel cu logica din jurul său. Să luăm următorul exemplu pentru a ilustra modul în care funcționează un bloc și, mai important, cum nu funcționează:

{foreach $posts as $post}
{block post}
	<h1>{$post->title}</h1>
	<p>{$post->body}</p>
{/block}
{/foreach}

Dacă ați reda acest șablon, rezultatul ar fi exact același cu sau fără etichetele de bloc. Blocurile au acces la variabilele din sferele exterioare. Aceasta este doar o modalitate de a face acest lucru suprascriptibil de către un șablon copil:

{layout 'parent.Latte'}

{block post}
	<article>
		<header>{$post->title}</header>
		<section>{$post->text}</section>
	</article>
{/block}

Acum, la redarea șablonului copil, bucla va utiliza blocul definit în șablonul copil child.Latte în loc de cel definit în șablonul de bază parent.Latte; șablonul executat este echivalent cu următorul:

{foreach $posts as $post}
	<article>
		<header>{$post->title}</header>
		<section>{$post->text}</section>
	</article>
{/foreach}

Cu toate acestea, dacă creăm o nouă variabilă în interiorul unui bloc numit sau înlocuim o valoare a uneia existente, modificarea va fi vizibilă numai în interiorul blocului:

{var $foo = 'foo'}
{block post}
	{do $foo = 'new value'}
	{var $bar = 'bar'}
{/block}

foo: {$foo}                  // prints: foo
bar: {$bar ?? 'not defined'} // prints: not defined

Conținutul blocului poate fi modificat prin filtre. Exemplul următor elimină tot codul HTML și îl pune în titlu:

<title>{block title|stripHtml|capitalize}...{/block}</title>

Eticheta poate fi scrisă și sub forma n:attribute:

<article n:block=post>
	...
</article>

Blocuri locale

Fiecare bloc suprascrie conținutul blocului părinte cu același nume. Cu excepția blocurilor locale. Acestea sunt ceva de genul metodelor private din clasă. Puteți crea un șablon fără să vă faceți griji că – din cauza coincidenței numelor blocurilor – acestea vor fi suprascrise de al doilea șablon.

{block local helper}
	...
{/block}

Imprimarea blocurilor {include}

Vezi și {include file}

Pentru a imprima un bloc într-un anumit loc, utilizați eticheta {include blockname}:

<title>{block title}{/block}</title>

<h1>{include title}</h1>

De asemenea, puteți afișa un bloc dintr-un alt șablon:

{include footer from 'main.latte'}

Blocul tipărit nu are acces la variabilele contextului activ, cu excepția cazului în care blocul este definit în același fișier în care este inclus. Cu toate acestea, ele au acces la variabilele globale.

Puteți transmite variabile către bloc în felul următor:

{include footer, foo: bar, id: 123}

Puteți utiliza o variabilă sau orice expresie din PHP ca nume de bloc. În acest caz, adăugați cuvântul cheie block înaintea variabilei, astfel încât să se știe la compilare că este vorba de un bloc și nu de un șablon de inserție, al cărui nume ar putea fi, de asemenea, în variabilă:

{var $name = footer}
{include block $name}

Blocul poate fi, de asemenea, tipărit în interiorul său, ceea ce este util, de exemplu, atunci când se redă o structură arborescentă:

{define menu, $items}
<ul>
	{foreach $items as $item}
		<li>
		{if is_array($item)}
			{include menu, $item}
		{else}
			{$item}
		{/if}
		</li>
	{/foreach}
</ul>
{/define}

În loc de {include menu, ...} se poate scrie și {include this, ...} unde this înseamnă blocul curent.

Conținutul tipărit poate fi modificat prin filtre. Exemplul următor elimină tot HTML-ul și pune titlul în majuscule:

<title>{include heading|stripHtml|capitalize}</title>

Bloc părinte

Dacă aveți nevoie să imprimați conținutul blocului din șablonul părinte, instrucțiunea {include parent} va face acest lucru. Acest lucru este util dacă doriți să adăugați la conținutul unui bloc părinte în loc să îl suprascrieți complet.

{block footer}
	{include parent}
	<a href="https://github.com/nette">GitHub</a>
	<a href="https://twitter.com/nettefw">Twitter</a>
{/block}

Definiții {define}

Pe lângă blocuri, există și “definiții” în Latte. Acestea sunt comparabile cu funcțiile din limbajele de programare obișnuite. Ele sunt utile pentru a reutiliza fragmente de șabloane pentru a nu vă repeta.

Latte încearcă să mențină lucrurile simple, așa că, în principiu, definițiile sunt la fel ca și blocurile și tot ceea ce se spune despre blocuri se aplică și la definiții. Acestea diferă de blocuri prin faptul că:

  1. sunt incluse în etichete {define}
  2. sunt redate doar atunci când sunt inserate prin {include}
  3. se pot defini parametri pentru ele ca și în cazul funcțiilor din PHP.
{block foo}<p>Hello</p>{/block}
{* prints: <p>Hello</p> *}

{define bar}<p>World</p>{/define}
{* nu imprimă nimic *}

{include bar}
{* imprimă: <p>World</p> *}

Imaginați-vă că aveți un șablon de ajutor cu o colecție de definiții privind modul de desenare a formularelor 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}

Argumentele unei definiții sunt întotdeauna opționale cu valoarea implicită null, cu excepția cazului în care se specifică valoarea implicită (aici 'text' este valoarea implicită pentru $type). Tipurile de parametri pot fi, de asemenea, declarate: {define input, string $name, ...}.

Șablonul cu definițiile este încărcat folosind {import}. Definițiile în sine sunt redate în același mod ca și blocurile:

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

Definițiile nu au acces la variabilele contextului activ, dar au acces la variabilele globale.

Nume de blocuri dinamice

Latte permite o mare flexibilitate în definirea blocurilor, deoarece numele blocului poate fi orice expresie PHP. Acest exemplu definește trei blocuri numite hi-Peter, hi-John și hi-Mary:

{foreach [Peter, John, Mary] as $name}
	{block "hi-$name"}Hi, I am {$name}.{/block}
{/foreach}

De exemplu, putem redefini doar un singur bloc într-un șablon copil:

{block hi-John}Hello. I am {$name}.{/block}

Astfel, rezultatul va arăta astfel:

Hi, I am Peter.
Hello. I am John.
Hi, I am Mary.

Verificarea existenței blocului {ifset}

A se vedea și {ifset $var}

Utilizați testul {ifset blockname} pentru a verifica dacă un bloc (sau mai multe blocuri) există în contextul curent:

{ifset footer}
	...
{/ifset}

{ifset footer, header, main}
	...
{/ifset}

Puteți utiliza o variabilă sau orice expresie din PHP ca nume de bloc. În acest caz, adăugați cuvântul cheie block înaintea variabilei pentru a clarifica faptul că nu variabila este cea care este verificată:

{ifset block $name}
	...
{/ifset}

Existența blocurilor este, de asemenea, returnată de funcția hasBlock():

{if hasBlock(header) || hasBlock(footer)}
	...
{/if}

Sfaturi

Iată câteva sfaturi pentru a lucra cu blocuri:

  • Ultimul bloc de nivel superior nu trebuie să aibă etichetă de închidere (blocul se termină odată cu sfârșitul documentului). Acest lucru simplifică scrierea șabloanelor copil, care un singur bloc primar.
  • Pentru o mai mare lizibilitate, puteți da opțional un nume etichetei {/block}, de exemplu {/block footer}. Cu toate acestea, numele trebuie să se potrivească cu numele blocului. În șabloanele mai mari, această tehnică vă ajută să vedeți care etichete de bloc sunt închise.
  • Nu puteți defini direct mai multe etichete de bloc cu același nume în același șablon. Dar acest lucru poate fi realizat folosind nume de bloc dinamice.
  • Puteți utiliza n:attributes pentru a defini blocuri precum <h1 n:block=title>Welcome to my awesome homepage</h1>
  • De asemenea, blocurile pot fi utilizate fără nume doar pentru a aplica filtrele la ieșire: {block|strip} hello {/block}

Reutilizare orizontală {import}

Reutilizarea orizontală este un al treilea mecanism de reutilizare și moștenire în Latte. Acesta vă permite să încărcați blocuri din alte șabloane. Este similar cu crearea unui fișier PHP cu funcții ajutătoare sau a unei trăsături.

Deși moștenirea aspectului șablonului este una dintre cele mai puternice caracteristici ale Latte, aceasta este limitată la moștenirea simplă – un șablon poate extinde doar un alt șablon. Reutilizarea orizontală este o modalitate de a obține moștenirea multiplă.

Să avem un set de definiții de blocuri:

{block sidebar}...{/block}

{block menu}...{/block}

Folosind comanda {import}, importați toate blocurile și definițiile definite în blocks.latte într-un alt șablon:

{import 'blocks.latte'}

{* blocurile sidebar și de meniu pot fi folosite acum *}

Dacă importați blocurile în șablonul părinte (de exemplu, utilizați {import} în layout.latte), blocurile vor fi disponibile și în toate șabloanele copil, ceea ce este foarte util.

Șablonul care se intenționează a fi importat (de exemplu, blocks.latte) nu trebuie să extindă un alt șablon, adică să utilizeze {layout}. Cu toate acestea, el poate importa alte șabloane.

Eticheta {import} trebuie să fie prima etichetă de șablon după {layout}. Numele șablonului poate fi orice expresie PHP:

{import $ajax ? 'ajax.latte' : 'not-ajax.latte'}

Puteți utiliza oricâte declarații {import} doriți în cadrul unui șablon dat. În cazul în care două șabloane importate definesc același bloc, primul câștigă. Cu toate acestea, prioritatea cea mai mare este acordată șablonului principal, care poate suprascrie orice bloc importat.

Toate blocurile suprascrise pot fi incluse treptat prin inserarea lor ca bloc părinte:

{layout 'layout.latte'}

{import 'blocks.latte'}

{block sidebar}
	{include parent}
{/block}

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

În acest exemplu, {include parent} va apela corect blocul sidebar din șablonul blocks.latte.

Moștenirea unității {embed}

Moștenirea unitară transpune ideea de moștenire a aspectului la nivelul fragmentelor de conținut. În timp ce moștenirea layout-ului funcționează cu “schelete de document”, care sunt aduse la viață de șabloanele copil, moștenirea unitară vă permite să creați schelete pentru unități mai mici de conținut și să le reutilizați oriunde doriți.

În cazul moștenirii unitare, eticheta {embed} este cheia. Aceasta combină comportamentul lui {include} și {layout}. Vă permite să includeți conținutul unui alt șablon sau bloc și, opțional, să treceți variabile, la fel ca {include}. De asemenea, vă permite să suprascrieți orice bloc definit în interiorul șablonului inclus, așa cum face {layout}.

De exemplu, vom folosi elementul acordeon pliabil. Să ne uităm la scheletul elementului din șablonul collapsible.latte:

<section class="collapsible {$modifierClass}">
	<h4 class="collapsible__title">
		{block title}{/block}
	</h4>

	<div class="collapsible__content">
		{block content}{/block}
	</div>
</section>

Eticheta {block} definește două blocuri pe care șabloanele copil le pot completa. Da, la fel ca în cazul șablonului părinte din șablonul de moștenire a aspectului. Vedeți și variabila $modifierClass.

Să folosim elementul nostru în șablon. Aici intervine {embed}. Este o piesă super puternică care ne permite să facem toate lucrurile: să includem conținutul șablonului elementului, să adăugăm variabile la acesta și să adăugăm blocuri cu HTML personalizat la acesta:

{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}

Rezultatul ar putea arăta astfel:

<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>

Blocurile din interiorul etichetelor embed formează un strat separat, independent de alte blocuri. Prin urmare, ele pot avea același nume ca și blocul din afara etichetei embed și nu sunt afectate în niciun fel. Utilizând tag-ul include în interiorul tag-urilor {embed} puteți insera blocuri create aici, blocuri din șablonul încorporat (care nu sunt locale), precum și blocuri din șablonul principal care sunt locale. De asemenea, puteți importa blocuri din alte fișiere:

{block outer}…{/block}
{block local hello}…{/block}

{embed 'collapsible.latte', modifierClass: my-style}
	{import 'blocks.latte'}

	{block inner}…{/block}

	{block title}
		{include inner} {* funcționează, blocul este definit în interiorul embed *}
		{include hello} {* funcționează, blocul este local în acest șablon *}
		{include content} {* funcționează, blocul este definit în șablonul încorporat *}
		{include aBlockDefinedInImportedTemplate} {* funcționează *}
		{include outer} {* nu funcționează! - blocul este în stratul exterior *}
	{/block}
{/embed}

Șabloanele încorporate nu au acces la variabilele din contextul activ, dar au acces la variabilele globale.

Cu {embed} puteți insera nu numai șabloane, ci și alte blocuri, astfel încât exemplul anterior ar putea fi scris astfel:

{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}

Dacă transmitem o expresie către {embed} și nu este clar dacă este vorba de un bloc sau de un nume de fișier, se adaugă cuvântul cheie block sau file:

{embed block $name} ... {/embed}

Cazuri de utilizare

Există diferite tipuri de moștenire și reutilizare a codului în Latte. Să rezumăm principalele concepte pentru mai multă claritate:

{include template}

Caz de utilizare: Utilizarea header.latte & footer.latte în interiorul 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}

Caz de utilizare: Extinderea layout.latte în interiorul 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}

Caz de utilizare: sidebar.latte în 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}

Caz de utilizare: O funcție care primește anumite variabile și emite anumite marcaje.

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}

Caz de utilizare: Integrarea pagination.latte în 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}
versiune: 3.0