Dědičnost a znovupoužitelnost šablon

Mechanismy dědičnosti a znovupoužitelnosti šablon v Latte významně zvyšují produktivitu vývojářů. Každá šablona tak může obsahovat pouze svůj jedinečný obsah, zatímco opakující se prvky a struktury se efektivně znovupoužívají. V této kapitole představíme tři klíčové koncepty: layoutovou dědičnost, horizontální znovupoužití a jednotkovou dědičnost.

Koncept dědičnosti šablon v Latte je analogický k dědičnosti tříd v PHP. Definujete nadřazenou šablonu, od které mohou další podřízené šablony dědit a případně přepisovat její části. Tento přístup je zvláště účinný, když různé prvky sdílejí společnou strukturu. Ačkoli to může znít složitě, v praxi jde o velmi intuitivní a snadno použitelný systém.

Layoutová dědičnost {layout}

Podívejme se na layoutovou dědičnost na konkrétním příkladu. Následující ukázka představuje nadřazenou šablonu, kterou můžeme nazvat například layout.latte. Tato šablona definuje základní kostru HTML dokumentu:

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

Značky {block} zde vymezují tři bloky, které mohou podřízené šablony naplnit vlastním obsahem. Blok v tomto kontextu jednoduše označuje místo, které může podřízená šablona přepsat definováním vlastního bloku se stejným názvem.

Podřízená šablona pak může vypadat například takto:

{layout 'layout.latte'}

{block title}Můj úžasný blog{/block}

{block content}
	<p>Vítejte na mé skvělé domovské stránce.</p>
{/block}

Klíčovým prvkem je zde značka {layout}. Ta Latte sděluje, že tato šablona „rozšiřuje“ jinou šablonu. Při vykreslování této šablony Latte nejprve nalezne nadřazenou šablonu – v tomto případě layout.latte.

V tomto okamžiku Latte identifikuje tři blokové značky v layout.latte a nahradí tyto bloky obsahem z podřízené šablony. Vzhledem k tomu, že podřízená šablona nedefinovala blok footer, použije se pro tento blok obsah z nadřazené šablony. Obsah uvnitř značky {block} v nadřazené šabloně vždy slouží jako výchozí, pokud není přepsán.

Výsledný výstup může vypadat následovně:

<!doctype html>
<html lang="en">
<head>
	<title>Můj úžasný blog</title>
	<link rel="stylesheet" href="style.css">
</head>
<body>
	<div id="content">
		<p>Vítejte na mé skvělé domovské stránce.</p>
	</div>
	<div id="footer">
		&copy; Copyright 2008
	</div>
</body>
</html>

V podřízené šabloně mohou být bloky umístěny pouze na nejvyšší úrovni nebo uvnitř jiného bloku, tj.:

{block content}
	<h1>{block title}Vítejte na mé úžasné domovské stránce{/block}</h1>
{/block}

Je důležité si uvědomit, že blok bude vždy vytvořen bez ohledu na to, zda je okolní {if} podmínka vyhodnocena jako pravdivá nebo nepravdivá. Takže i když to tak na první pohled nevypadá, následující šablona blok skutečně definuje:

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

Pokud chcete, aby se obsah uvnitř bloku zobrazoval podmíněně, použijte místo toho následující přístup:

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

Prostor mimo bloky v podřízené šabloně se zpracovává před vykreslením šablony layoutu. Můžete jej tedy využít k definování proměnných pomocí {var $foo = bar} a k šíření dat do celého řetězce dědičnosti:

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

...

Víceúrovňová dědičnost

Latte umožňuje použít tolik úrovní dědičnosti, kolik potřebujete. Běžný způsob využití layoutové dědičnosti je následující tříúrovňový přístup:

  1. Vytvořte šablonu layout.latte, která obsahuje hlavní kostru vzhledu webu.
  2. Vytvořte šablonu layout-SECTIONNAME.latte pro každou sekci vašeho webu. Například layout-news.latte, layout-blog.latte atd. Všechny tyto šablony rozšiřují layout.latte a zahrnují styly a design specifické pro jednotlivé sekce.
  3. Vytvořte individuální šablony pro každý typ stránky, například novinový článek nebo položku blogu. Tyto šablony rozšiřují příslušnou šablonu sekce.

Dynamická dědičnost

Jako název nadřazené šablony lze použít proměnnou nebo jakýkoli výraz PHP, což umožňuje dynamické chování dědičnosti:

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

Latte API také nabízí možnost automatického výběru šablony layoutu.

Tipy pro práci s layoutovou dědičností

Zde je několik užitečných tipů pro efektivní práci s layoutovou dědičností:

  • Pokud v šabloně použijete {layout}, musí to být první značka šablony.
  • Layout lze dohledávat automaticky (např. v presenterech). V takovém případě, pokud šablona nemá mít layout, oznámí to značkou {layout none}.
  • Značka {layout} má alias {extends}.
  • Název souboru layoutu závisí na použitém loaderu.
  • Můžete definovat libovolný počet bloků. Pamatujte, že podřízené šablony nemusí definovat všechny nadřazené bloky, takže můžete nastavit vhodné výchozí hodnoty v několika blocích a později definovat pouze ty, které potřebujete upravit.

Bloky {block}

Viz také anonymní {block}

Blok představuje způsob, jak změnit způsob vykreslování určité části šablony, aniž by to ovlivnilo logiku kolem něj. Následující příklad ilustruje, jak blok funguje a také jeho omezení:

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

Při vykreslení této šablony bude výsledek identický s verzí bez značek {block} i s nimi. Bloky mají přístup k proměnným z vnějších oborů. Jejich hlavním účelem je poskytnout možnost přepsání obsahu v podřízené šabloně:

{layout 'parent.Latte'}

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

Nyní při vykreslování podřízené šablony bude smyčka používat blok definovaný v child.Latte místo bloku v parent.Latte. Výsledná šablona je ekvivalentní následujícímu kódu:

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

Je důležité si uvědomit, že pokud vytvoříte novou proměnnou uvnitř pojmenovaného bloku nebo změníte hodnotu existující proměnné, tato změna bude viditelná pouze uvnitř daného bloku:

{var $foo = 'foo'}
{block post}
	{do $foo = 'nová hodnota'}
	{var $bar = 'bar'}
{/block}

foo: {$foo}                   // vypíše: foo
bar: {$bar ?? 'nedefinováno'} // vypíše: nedefinováno

Obsah bloku lze upravit pomocí filtrů. Následující příklad odstraní všechny HTML značky a změní velikost písmen:

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

Značku lze také zapsat jako n:attribut:

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

Lokální bloky

Každý blok standardně přepisuje obsah nadřazeného bloku se stejným názvem – s výjimkou lokálních bloků. Ty fungují podobně jako privátní metody ve třídách. Díky nim můžete vytvářet šablonu bez obav, že by kvůli shodě jmen bloků došlo k nechtěnému přepsání z jiné šablony.

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

Vykreslení bloků {include}

Viz také {include file}

Pro vypsání bloku na určitém místě použijte značku {include blockname}:

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

<h1>{include title}</h1>

Můžete také vypsat blok z jiné šablony:

{include footer from 'main.latte'}

Vykreslovaný blok nemá přístup k proměnným aktivního kontextu, s výjimkou případů, kdy je blok definován ve stejném souboru, kde je i vložen. Má však přístup ke globálním proměnným.

Proměnné můžete do bloku předávat tímto způsobem:

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

Jako název bloku lze použít proměnnou nebo jakýkoli výraz v PHP. V takovém případě před proměnnou doplníme klíčové slovo block, aby Latte vědělo, že jde o blok, a nikoli o vkládání šablony, jejíž název by také mohl být v proměnné:

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

Blok lze vykreslit i uvnitř sebe samého, což je užitečné například při vykreslování stromové struktury:

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

Místo {include menu, ...} můžeme také napsat {include this, ...}, kde this odkazuje na aktuální blok.

Vykreslovaný blok lze upravit pomocí filtrů. Následující příklad odstraní všechny HTML značky a změní velikost písmen:

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

Rodičovský blok

Pokud potřebujete vypsat obsah bloku z nadřazené šablony, použijte {include parent}. To je užitečné, pokud chcete pouze doplnit obsah nadřazeného bloku místo jeho úplného přepsání.

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

Definice {define}

Kromě bloků existují v Latte také „definice". V běžných programovacích jazycích by se daly přirovnat k funkcím. Jsou užitečné pro opakované použití fragmentů šablony, čímž se vyhnete duplikaci kódu.

Latte se snaží zjednodušovat věci, takže v zásadě jsou definice stejné jako bloky a vše, co platí pro bloky, platí také pro definice. Liší se od bloků v následujících aspektech:

  1. jsou uzavřeny ve značkách {define}
  2. vykreslí se teprve, až když je vložíte přes {include}
  3. lze jim definovat parametry podobně jako funkcím v PHP
{block foo}<p>Hello</p>{/block}
{* vypíše: <p>Hello</p> *}

{define bar}<p>World</p>{/define}
{* nevypíše nic *}

{include bar}
{* vypíše: <p>World</p> *}

Představte si, že máte pomocnou šablonu s kolekcí definic pro vykreslování HTML formulářů:

{define input, $name, $value, $type = 'text'}
	<input type={$type} name={$name} value={$value}>
{/define}

{define textarea, $name, $value}
	<textarea name={$name}>{$value}</textarea>
{/define}

Argumenty jsou vždy volitelné s výchozí hodnotou null, pokud není uvedena explicitní výchozí hodnota (zde 'text' je výchozí hodnota pro $type). Můžete také deklarovat typy parametrů: {define input, string $name, ...}.

Šablonu s definicemi načteme pomocí {import}. Samotné definice se vykreslují stejným způsobem jako bloky:

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

Definice nemají přístup k proměnným aktivního kontextu, ale mají přístup ke globálním proměnným.

Dynamické názvy bloků

Latte nabízí velkou flexibilitu při definování bloků, protože název bloku může být jakýkoli výraz PHP. Tento příklad definuje tři bloky s názvy hi-Peter, hi-John a hi-Mary:

{foreach [Peter, John, Mary] as $name}
	{block "hi-$name"}Ahoj, já jsem {$name}.{/block}
{/foreach}

V podřízené šabloně pak můžeme předefinovat například jen jeden blok:

{block hi-John}Zdravím. Jmenuji se {$name}.{/block}

Výstup bude vypadat takto:

Ahoj, já jsem Peter.
Zdravím. Jmenuji se John.
Ahoj, já jsem Mary.

Kontrola existence bloků {ifset}

Viz také {ifset $var}

Pomocí testu {ifset blockname} můžete zkontrolovat, zda v aktuálním kontextu existuje blok (nebo více bloků):

{ifset footer}
	...
{/ifset}

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

Jako název bloku lze použít proměnnou nebo jakýkoli výraz v PHP. V takovém případě před proměnnou doplníme klíčové slovo block, aby bylo jasné, že nejde o test existence proměnných:

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

Existenci bloků ověřuje také funkce hasBlock():

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

Tipy pro práci s bloky

Několik užitečných tipů pro efektivní práci s bloky:

  • Poslední blok nejvyšší úrovně nemusí mít uzavírací značku (blok končí koncem dokumentu). To zjednodušuje psaní podřízených šablon, které obsahují jeden primární blok.
  • Pro lepší čitelnost můžete název bloku uvést ve značce {/block}, například {/block footer}. Název se však musí shodovat s názvem bloku. Ve větších šablonách vám tato technika pomůže snadno identifikovat, které bloky se uzavírají.
  • Ve stejné šabloně nemůžete přímo definovat více značek bloků se stejným názvem. Toho však lze dosáhnout pomocí dynamických názvů bloků.
  • Můžete použít n:atributy k definování bloků, například: <h1 n:block=title>Vítejte na mé úžasné domovské stránce</h1>
  • Bloky lze také použít bez názvů pouze k aplikaci filtrů{block|strip} hello {/block}

Horizontální znovupoužití {import}

Horizontální znovupoužití je v Latte třetím mechanismem pro opětovné použití a dědičnost. Umožňuje načítat bloky z jiných šablon. Je to podobné, jako když si v PHP vytvoříme soubor s pomocnými funkcemi, který potom načítáme pomocí require.

I když je layoutová dědičnost šablony jednou z nejsilnějších funkcí Latte, je omezena na jednoduchou dědičnost – šablona může rozšířit pouze jednu další šablonu. Horizontální znovupoužití je způsob, jak dosáhnout vícenásobné dědičnosti.

Mějme soubor s definicemi bloků:

{block sidebar}...{/block}

{block menu}...{/block}

Pomocí příkazu {import} naimportujeme všechny bloky a definice definované v blocks.latte do jiné šablony:

{import 'blocks.latte'}

{* nyní lze použít bloky sidebar a menu *}

Pokud bloky importujete v nadřazené šabloně (tj. použijete {import} v layout.latte), budou bloky k dispozici i ve všech podřízených šablonách, což je velmi praktické.

Šablona, která je určena k importování (např. blocks.latte), nesmí rozšiřovat další šablonu, tj. používat {layout}. Může však importovat další šablony.

Značka {import} by měla být první značkou šablony po {layout}. Název šablony může být jakýkoli výraz PHP:

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

V šabloně můžete použít tolik {import} příkazů, kolik potřebujete. Pokud dvě importované šablony definují stejný blok, má přednost první z nich. Nejvyšší prioritu má ale hlavní šablona, která může přepsat jakýkoli importovaný blok.

Obsah přepsaných bloků se dá zachovat tak, že blok vložíme stejně, jako se vkládá rodičovský blok:

{layout 'layout.latte'}

{import 'blocks.latte'}

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

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

V tomto příkladu {include parent} zavolá blok sidebar ze šablony blocks.latte.

Jednotková dědičnost {embed}

Jednotková dědičnost rozšiřuje koncept layoutové dědičnosti na úroveň fragmentů obsahu. Zatímco layoutová dědičnost pracuje s „kostrou dokumentu“, kterou oživují podřízené šablony, jednotková dědičnost vám umožňuje vytvářet znovupoužitelné kostry pro menší jednotky obsahu a používat je kdekoli potřebujete.

Klíčovým prvkem jednotkové dědičnosti je značka {embed}. Ta kombinuje funkčnost {include} a {layout}. Umožňuje vložit obsah jiné šablony či bloku a volitelně předat proměnné, stejně jako v případě {include}. Zároveň umožňuje přepsat libovolný blok definovaný uvnitř vložené šablony, podobně jako při použití {layout}.

Podívejme se na příklad s prvkem akordeon. Nejprve definujeme kostru prvku v šabloně collapsible.latte:

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

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

Značky {block} definují dva bloky, které mohou podřízené šablony naplnit. Fungují stejně jako v případě nadřazené šablony v layoutové dědičnosti. Všimněte si také proměnné $modifierClass.

Nyní použijeme náš prvek v šabloně. Zde přichází ke slovu {embed}. Tato výkonná značka nám umožňuje provést několik věcí najednou: vložit obsah šablony prvku, přidat do něj proměnné a definovat bloky s vlastním 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}

Výstup může vypadat takto:

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

Bloky uvnitř vložených značek tvoří samostatnou vrstvu nezávislou na ostatních blocích. Proto mohou mít stejný název jako blok mimo vložení a nejsou jím nijak ovlivněny. Pomocí značky include uvnitř značek {embed} můžete vložit bloky zde vytvořené, bloky z vložené šablony (které nejsou lokální) a také bloky z hlavní šablony, které naopak jsou lokální. Můžete také importovat bloky z jiných souborů:

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

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

	{block inner}…{/block}

	{block title}
		{include inner} {* funguje, blok je definován uvnitř embed *}
		{include hello} {* funguje, blok je lokální v této šabloně *}
		{include content} {* funguje, blok je definován ve vložené šabloně *}
		{include aBlockDefinedInImportedTemplate} {* funguje *}
		{include outer} {* nefunguje! - blok je ve vnější vrstvě *}
	{/block}
{/embed}

Vložené šablony nemají přístup k proměnným aktivního kontextu, ale mají přístup ke globálním proměnným.

Pomocí {embed} lze vkládat nejen šablony, ale i jiné bloky. Předchozí příklad by se dal zapsat tímto způsobem:

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

Pokud do {embed} předáme výraz a není zřejmé, jestli jde o název bloku nebo souboru, doplníme klíčové slovo block nebo file:

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

Případy použití

V Latte existují různé typy dědičnosti a opětovného použití kódu. Pojďme si shrnout hlavní koncepty pro lepší pochopení:

{include template}

Případ použití: Použití header.latte a footer.latte uvnitř 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}

Případ použití: Rozšíření layout.latte uvnitř homepage.latte a about.latte.

layout.latte

{include 'header.latte'}

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

{include 'footer.latte'}

homepage.latte

{layout 'layout.latte'}

{block main}
	<p>Domovská stránka</p>
{/block}

about.latte

{layout 'layout.latte'}

{block main}
	<p>Stránka O nás</p>
{/block}

{import}

Případ použití: Použití sidebar.latte v single.product.latte a single.service.latte.

sidebar.latte

{block sidebar}<aside>Toto je postranní panel</aside>{/block}

single.product.latte

{layout 'product.layout.latte'}

{import 'sidebar.latte'}

{block main}<main>Stránka produktu</main>{/block}

single.service.latte

{layout 'service.layout.latte'}

{import 'sidebar.latte'}

{block main}<main>Stránka služby</main>{/block}

{define}

Případ použití: Funkce, kterým předáme proměnné a něco vykreslí.

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

{embed}

Případ použití: Vložení pagination.latte do product.table.latte a 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}První stránka produktů{/block}
	{block last}Poslední stránka produktů{/block}
{/embed}

service.table.latte

{embed 'pagination.latte', min: 1, max: $services->count}
	{block first}První stránka služeb{/block}
	{block last}Poslední stránka služeb{/block}
{/embed}

Tyto příklady ilustrují, jak různé mechanismy dědičnosti a znovupoužitelnosti v Latte spolupracují, aby umožnily vytváření flexibilních, modulárních a snadno udržovatelných šablon. Každý z těchto přístupů má své specifické použití:

  1. {include template} je užitečný pro vkládání menších, opakujících se částí stránek, jako jsou hlavičky a patičky.
  2. {layout} umožňuje vytvářet konzistentní strukturu napříč různými stránkami webu, přičemž jednotlivé stránky mohou upravovat specifické části.
  3. {import} je skvělý pro sdílení bloků kódu mezi různými šablonami, což podporuje princip DRY (Don't Repeat Yourself).
  4. {define} je ideální pro vytváření znovupoužitelných komponent, které přijímají parametry, jako jsou formulářové prvky.
  5. {embed} kombinuje flexibilitu vkládání s možností přepisovat části vloženého obsahu, což je užitečné pro komplexnější komponenty jako je stránkování.

Použitím těchto technik můžete vytvářet vysoce modulární, flexibilní a snadno udržovatelné šablony pro vaše webové aplikace. Latte tak nabízí pokročilé možnosti pro strukturování šablon, které uspokojí potřeby i těch nejnáročnějších projektů.

verze: 3.0 2.x