Moștenirea și reutilizarea șabloanelor

Mecanismele de reutilizare și moștenire a șabloanelor vă vor crește productivitatea, deoarece fiecare șablon conține doar conținutul său unic, iar elementele și structurile repetitive sunt reutilizate. Vă prezentăm trei concepte: Moștenirea layout-ului, Reutilizarea orizontală și Moștenirea unitară.

Conceptul de moștenire a șabloanelor Latte este similar cu moștenirea claselor în PHP. Definiți un șablon părinte, de la care pot moșteni alte șabloane copil și pot suprascrie părți ale șablonului părinte. Funcționează excelent atunci când elementele partajează o structură comună. Sună complicat? Nu vă faceți griji, este foarte ușor.

Moștenirea layout-ului {layout}

Să vedem moștenirea șablonului de layout, adică a layout-ului, direct printr-un exemplu. Acesta este șablonul părinte, pe care îl vom numi, de exemplu, layout.latte și care definește scheletul documentului 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>

Tag-urile {block} definesc trei blocuri pe care șabloanele copil le pot completa. Tag-ul block face doar atât: anunță că acest loc poate fi suprascris de șablonul copil prin definirea propriului bloc cu același nume.

Un șablon copil poate arăta astfel:

{layout 'layout.latte'}

{block title}Blogul meu uimitor{/block}

{block content}
	<p>Bun venit pe pagina mea minunată.</p>
{/block}

Cheia aici este tag-ul {layout}. Acesta îi spune lui Latte că acest șablon „extinde” un alt șablon. Când Latte redă acest șablon, mai întâi găsește șablonul părinte – în acest caz layout.latte.

În acest moment, Latte observă cele trei tag-uri block în layout.latte și înlocuiește aceste blocuri cu conținutul șablonului copil. Deoarece șablonul copil nu a definit blocul footer, se va folosi în schimb conținutul din șablonul părinte. Conținutul din tag-ul {block} în șablonul părinte este întotdeauna folosit ca alternativă.

Outputul poate arăta astfel:

<!doctype html>
<html lang="en">
<head>
	<title>Blogul meu uimitor</title>
	<link rel="stylesheet" href="style.css">
</head>
<body>
	<div id="content">
		<p>Bun venit pe pagina mea minunată.</p>
	</div>
	<div id="footer">
		&copy; Copyright 2008
	</div>
</body>
</html>

În șablonul copil, blocurile pot fi plasate doar la nivelul superior sau în interiorul altui bloc, adică:

{block content}
	<h1>{block title}Bun venit pe pagina mea minunată{/block}</h1>
{/block}

De asemenea, un bloc va fi întotdeauna creat indiferent dacă condiția {if} înconjurătoare este evaluată ca adevărată sau falsă. Deci, chiar dacă nu pare așa, acest șablon va defini blocul.

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

Dacă doriți ca outputul 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}

Spațiul din afara blocurilor în șablonul copil este executat înainte de redarea șablonului de layout, astfel încât îl puteți folosi 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ștenire pe mai multe niveluri

Puteți utiliza oricâte niveluri de moștenire aveți nevoie. O modalitate obișnuită de a utiliza moștenirea layout-ului este următoarea abordare pe trei niveluri:

  1. Creați un șablon layout.latte care conține scheletul principal al aspectului site-ului.
  2. Creați un șablon layout-SECTIONNAME.latte pentru fiecare secțiune a site-ului dvs. 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ă, de exemplu, un articol de știri sau o postare de blog. Aceste șabloane extind șablonul de secțiune corespunzător.

Moștenire dinamică

Ca nume al șablonului părinte se poate folosi o variabilă sau orice expresie PHP, astfel încât moștenirea se poate comporta dinamic:

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

Puteți utiliza, de asemenea, API-ul Latte pentru a selecta automat șablonul de layout.

Sfaturi

Iată câteva sfaturi pentru lucrul cu moștenirea layout-ului:

  • Dacă utilizați {layout} într-un șablon, acesta trebuie să fie primul tag de șablon din acel șablon.
  • Layout-ul poate fi căutat automat (așa cum se întâmplă, de exemplu, în presentere). În acest caz, dacă șablonul nu trebuie să aibă un layout, acesta o anunță prin tag-ul {layout none}.
  • Tag-ul {layout} are un alias {extends}.
  • Numele fișierului de layout depinde de loader-ului.
  • Puteți avea oricâte blocuri doriți. Amintiți-vă că șabloanele copil nu trebuie să definească toate blocurile părinte, așa că puteți completa valori implicite rezonabile în mai multe blocuri și apoi să definiți doar cele de care aveți nevoie mai târziu.

Blocuri {block}

Vezi și {block} anonim

Un bloc reprezintă o modalitate de a schimba modul în care este redată o anumită parte a șablonului, dar nu interferează în niciun fel cu logica din jurul său. În exemplul următor, vom arăta cum funcționează un bloc, dar și cum nu funcționează:

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

Dacă redați acest șablon, rezultatul va fi exact același cu sau fără tag-urile {block}. Blocurile au acces la variabilele din domeniile exterioare. Ele doar oferă posibilitatea de a fi suprascrise de 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 folosi blocul definit în șablonul copil child.Latte în locul blocului definit în parent.Latte; șablonul executat este apoi 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 valoarea uneia existente, modificarea va fi vizibilă doar în interiorul blocului:

{var $foo = 'foo'}
{block post}
	{do $foo = 'valoare nouă'}
	{var $bar = 'bar'}
{/block}

foo: {$foo}                  // afișează: foo
bar: {$bar ?? 'nedefinit'}   // afișează: nedefinit

Conținutul blocului poate fi modificat folosind filtre. Următorul exemplu elimină tot HTML-ul și schimbă majusculele/minusculele:

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

Tag-ul poate fi scris și ca n:attribut:

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

Blocuri locale

Fiecare bloc suprascrie conținutul blocului părinte cu același nume – cu excepția blocurilor locale. În clase, acestea ar fi ceva de genul metodelor private. Astfel, puteți crea șablonul fără teama că, din cauza potrivirii numelor blocurilor, acestea ar fi suprascrise din alt șablon.

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

Redarea blocurilor {include}

Vezi și {include file}

Pentru a afișa un bloc într-un anumit loc, utilizați tag-ul {include blockname}:

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

<h1>{include title}</h1>

Se poate afișa și un bloc dintr-un alt șablon:

{include footer from 'main.latte'}

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

Puteți transmite variabile blocului în acest mod:

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

Ca nume de bloc se poate folosi o variabilă sau orice expresie PHP. În acest caz, înainte de variabilă adăugăm cuvântul cheie block, pentru ca Latte să știe deja la momentul compilării că este vorba de un bloc și nu de includerea șablonului, al cărui nume ar putea fi, de asemenea, într-o variabilă:

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

Blocul poate fi redat și în interiorul său, ceea ce este util, de exemplu, la redarea unei structuri arborescente:

{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, ...} putem scrie {include this, ...}, unde this înseamnă blocul curent.

Blocul redat poate fi modificat folosind filtre. Următorul exemplu elimină tot HTML-ul și schimbă majusculele/minusculele:

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

Blocul părinte

Dacă aveți nevoie să afișați conținutul unui bloc din șablonul părinte, utilizați {include parent}. Acest lucru este util dacă doriți doar să completați conținutul blocului 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, în Latte există și „definiții”. În limbajele de programare obișnuite, le-am compara cu funcțiile. Sunt utile pentru reutilizarea fragmentelor de șablon, pentru a nu vă repeta.

Latte încearcă să facă lucrurile simple, așa că, în principiu, definițiile sunt la fel ca blocurile și tot ce s-a spus despre blocuri se aplică și definițiilor. Se diferențiază de blocuri prin faptul că:

  1. sunt închise în tag-uri {define}
  2. se redau doar atunci când le includeți prin {include}
  3. li se pot defini parametri similar funcțiilor din PHP
{block foo}<p>Hello</p>{/block}
{* afișează: <p>Hello</p> *}

{define bar}<p>World</p>{/define}
{* nu afișează nimic *}

{include bar}
{* afișează: <p>World</p> *}

Imaginați-vă că aveți un șablon auxiliar cu o colecție de definiții despre cum să desenați formulare 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 sunt întotdeauna opționale cu valoarea implicită null, dacă nu este specificată o valoare implicită (aici 'text' este valoarea implicită pentru $type). Se pot declara și tipurile parametrilor: {define input, string $name, ...}.

Încărcăm șablonul cu definiții folosind {import}. Definițiile în sine se redau în același mod ca 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.

Denumiri dinamice de blocuri

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

{foreach [Peter, John, Mary] as $name}
	{block "hi-$name"}Salut, sunt {$name}.{/block}
{/foreach}

În șablonul copil, putem apoi redefini, de exemplu, doar un singur bloc:

{block hi-John}Bună. Sunt {$name}.{/block}

Deci outputul va arăta astfel:

Salut, sunt Peter.
Bună. Sunt John.
Salut, sunt Mary.

Verificarea existenței blocurilor {ifset}

Vezi și {ifset $var}

Folosind testul {ifset blockname}, verificăm dacă în contextul curent există un bloc (sau mai multe blocuri):

{ifset footer}
	...
{/ifset}

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

Ca nume de bloc se poate folosi o variabilă sau orice expresie PHP. În acest caz, înainte de variabilă adăugăm cuvântul cheie block, pentru a fi clar că nu este vorba de un test de existență a variabilelor:

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

Existența blocurilor este verificată și de funcția hasBlock():

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

Sfaturi

Câteva sfaturi pentru lucrul cu blocurile:

  • Ultimul bloc de nivel superior nu trebuie să aibă un tag de închidere (blocul se termină la sfârșitul documentului). Acest lucru simplifică scrierea șabloanelor copil care conțin un singur bloc principal.
  • Pentru o mai bună lizibilitate, puteți specifica numele blocului în tag-ul {/block}, de exemplu {/block footer}. Cu toate acestea, numele trebuie să corespundă numelui blocului. În șabloanele mai mari, această tehnică vă ajută să vedeți ce tag-uri de bloc se închid.
  • Nu puteți defini direct mai multe tag-uri de bloc cu același nume în același șablon. Acest lucru se poate realiza însă folosind denumiri dinamice de blocuri.
  • Puteți utiliza n:atribute pentru a defini blocuri precum <h1 n:block=title>Bun venit pe pagina mea minunată</h1>
  • Blocurile pot fi folosite și fără nume doar pentru a aplica filtre{block|strip} hello {/block}

Reutilizarea orizontală {import}

Reutilizarea orizontală este în Latte al treilea mecanism pentru reutilizare și moștenire. Permite încărcarea blocurilor din alte șabloane. Este similar cu crearea unui fișier cu funcții auxiliare în PHP, pe care apoi îl încărcăm folosind require.

Deși moștenirea layout-ului șablonului este una dintre cele mai puternice funcții Latte, este limitată la moștenire simplă – un șablon poate extinde doar un alt șablon. Reutilizarea orizontală este o modalitate de a obține moștenire multiplă.

Să avem un fișier cu definiții de blocuri:

{block sidebar}...{/block}

{block menu}...{/block}

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

{import 'blocks.latte'}

{* acum se pot folosi blocurile sidebar și menu *}

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

Șablonul destinat importării (de exemplu, blocks.latte) nu trebuie să extindă un alt șablon, adică să folosească {layout}. Cu toate acestea, poate importa alte șabloane.

Tag-ul {import} ar trebui să fie primul tag de șablon după {layout}. Numele șablonului poate fi orice expresie PHP:

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

În șablon puteți utiliza oricâte comenzi {import} doriți. Dacă două șabloane importate definesc același bloc, primul câștigă. Prioritatea cea mai mare o are însă șablonul principal, care poate suprascrie orice bloc importat.

Conținutul blocurilor suprascrise poate fi păstrat prin includerea blocului în același mod în care se include blocul 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 blocul sidebar din șablonul blocks.latte.

Moștenirea unitară {embed}

Moștenirea unitară extinde ideea moștenirii layout-ului la nivelul fragmentelor de conținut. În timp ce moștenirea layout-ului lucrează cu „scheletul documentului”, care este animat 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 moștenirea unitară, cheia este tag-ul {embed}. Acesta combină comportamentul {include} și {layout}. Permite includerea conținutului unui alt șablon sau bloc și, opțional, transmiterea variabilelor, la fel ca în cazul {include}. Permite, de asemenea, suprascrierea oricărui bloc definit în interiorul șablonului inclus, la fel ca la utilizarea {layout}.

De exemplu, vom folosi elementul acordeon. Să vedem scheletul elementului stocat în șablonul collapsible.latte:

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

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

Tag-urile {block} definesc două blocuri pe care șabloanele copil le pot completa. Da, la fel ca în cazul șablonului părinte în moștenirea layout-ului. Vedeți și variabila $modifierClass.

Să folosim elementul nostru în șablon. Aici intervine {embed}. Este un tag extrem de puternic care ne permite să facem toate lucrurile: să includem conținutul șablonului elementului, să adăugăm variabile în el și să adăugăm blocuri cu propriul HTML în el:

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

Outputul poate 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 tag-urilor incluse formează un strat separat, independent de celelalte blocuri. Prin urmare, pot avea același nume ca un bloc din afara includerii și nu sunt afectate în niciun fel. Folosind tag-ul include în interiorul tag-urilor {embed}, puteți include blocurile create aici, blocurile din șablonul inclus (care nu sunt locale) și, de asemenea, blocurile din șablonul principal, care, dimpotrivă, sunt locale. Puteți, de asemenea, 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 inclus *}
		{include aBlockDefinedInImportedTemplate} {* funcționează *}
		{include outer} {* nu funcționează! - blocul este în stratul exterior *}
	{/block}
{/embed}

Șabloanele incluse nu au acces la variabilele contextului activ, dar au acces la variabilele globale.

Folosind {embed} se pot include nu doar șabloane, ci și alte blocuri, deci exemplul anterior s-ar putea scrie în acest mod:

{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 în {embed} și nu este clar dacă este vorba de numele unui bloc sau al unui fișier, adăugăm cuvântul cheie block sau file:

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

Cazuri de utilizare

În Latte există diferite tipuri de moștenire și reutilizare a codului. Să rezumăm principalele concepte pentru o mai mare claritate:

{include template}

Caz de utilizare: Utilizarea header.latte și footer.latte în interiorul layout.latte.

header.latte

<nav>
   <div>Acasă</div>
   <div>Despre</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 și about.latte.

layout.latte

{include 'header.latte'}

<main>{block main}{/block}</main>

{include 'footer.latte'}

homepage.latte

{layout 'layout.latte'}

{block main}
	<p>Pagină de pornire</p>
{/block}

about.latte

{layout 'layout.latte'}

{block main}
	<p>Pagină despre</p>
{/block}

{import}

Caz de utilizare: sidebar.latte în single.product.latte și single.service.latte.

sidebar.latte

{block sidebar}<aside>Aceasta este bara laterală</aside>{/block}

single.product.latte

{layout 'product.layout.latte'}

{import 'sidebar.latte'}

{block main}<main>Pagină produs</main>{/block}

single.service.latte

{layout 'service.layout.latte'}

{import 'sidebar.latte'}

{block main}<main>Pagină serviciu</main>{/block}

{define}

Caz de utilizare: Funcții cărora le transmitem variabile și care redau ceva.

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, Trimite, submit}</div>
</form>

{embed}

Caz de utilizare: Includerea pagination.latte în product.table.latte și 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}Prima pagină de produse{/block}
	{block last}Ultima pagină de produse{/block}
{/embed}

service.table.latte

{embed 'pagination.latte', min: 1, max: $services->count}
	{block first}Prima pagină de servicii{/block}
	{block last}Ultima pagină de servicii{/block}
{/embed}
versiune: 3.0