Dědičnost a znovupoužitelnost šablon

Mechanismy opětovného použití a dědičnosti šablon zvýší vaši produktivitu, protože každá šablona obsahuje pouze svůj jedinečný obsah a opakované prvky a struktury se znovupoužijí. Představujeme tři koncepty: layoutová dědičnost, horizontální znovupoužití a jednotková dědičnost.

Koncept dědičnosti šablon Latte je podobný dědičnosti tříd v PHP. Definujete nadřazenou šablonu, od které mohou dědit další podřízené šablony a mohou přepsat části nadřazené šablony. Funguje to skvěle, když prvky sdílejí společnou strukturu. Zní to komplikovaně? Nebojte se, je to velmi snadné.

Layoutová dědičnost {layout}

Podívejme se na dědičnost šablony rozložení, tedy layoutu, rovnou příkladem. Toto je nadřazená šablona, kterou budeme nazývat například layout.latte a která definuje 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} definují tři bloky, které mohou podřízené šablony vyplnit. Značka block dělá jen to, že oznámí, že toto místo může podřízená šablona přepsat definováním vlastního bloku se stejným názvem.

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

{layout 'layout.latte'}

{block title}My amazing blog{/block}

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

Klíčem je zde značka {layout}. Říká Latte, že tato šablona „rozšiřuje“ další šablonu. Když Latte vykresluje tuto šablonu, nejprve najde rodiče – v tomto případě layout.latte.

V tomto okamžiku si Latte všimne tří blokových značek v layout.latte a nahradí tyto bloky obsahem podřízené šablony. Vzhledem k tomu, že podřízená šablona nedefinovala blok footer, použije se místo toho obsah z nadřazené šablony. Obsah ve značce {block} v nadřazené šabloně se vždy používá jako záložní.

Výstup může vypadat takto:

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

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}Welcome to my awesome homepage{/block}</h1>
{/block}

Také bude vždy vytvořen blok bez ohledu na to, zda je okolní {if} podmínka vyhodnocena jako pravdivá nebo nepravdivá. Takže i když to tak nevypadá, tato šablona blok nadefinuje.

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

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

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

Prostor mimo bloky v podřízené šabloně se provádí před vykreslením šablony layoutu, takže je můžete použít k definování proměnných jako {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

Můžete použít tolik úrovní dědičnosti, kolik potřebujete. Běžný způsob použ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 svého webu. Například layout-news.latte, layout-blog.latte atd. Všechny tyto šablony rozšiřují layout.latte a zahrnují styly & 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, takže dědičnost se může chovat dynamicky:

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

Můžete také použít Latte API k automatickému výběru šablony layoutu.

Tipy

Zde je několik tipů pro práci s layoutovou dědičností:

  • Pokud v šabloně použijete {layout}, musí to být první značka šablony v této šabloně.
  • Značka {layout} má alias {extends}.
  • Název souboru layoutu závisí na loaderu.
  • Můžete mít tolik bloků, kolik chcete. Pamatujte, že podřízené šablony nemusí definovat všechny nadřazené bloky, takže můžete vyplnit přiměřené výchozí hodnoty v několika blocích a poté definovat pouze ty, které potřebujete později.

Bloky {block}

Blok představuje způsob, jak změnit způsob vykreslování určité části šablony, ale nijak nezasahuje do logiky kolem něj. V následujícím příkladu si ukážeme, jak blok funguje, ale také jak nefunguje:

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

Pokud tuto šablonu vykreslíte, bude výsledek přesně stejný se značkami {block} i bez nich. Bloky mají přístup k proměnným z vnějších oborů. Jen dávají možnost se nechat přepsat podřízenou šablonou:

{* child.Latte *}
{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 podřízené šabloně child.Latte namísto bloku definovaného v parent.Latte; spuštěná šablona je pak ekvivalentní následující:

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

Pokud však vytvoříme novou proměnnou uvnitř pojmenovaného bloku nebo nahradíme hodnotu stávajícího, změna bude viditelná pouze uvnitř bloku:

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

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

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

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

Lokální bloky

Každý blok přepisuje obsah nadřazeného bloku se stejným názvem – kromě lokálních bloků. Ve třídách by šlo o něco jako privátní metody. Šablonu tak můžete tvořit bez obav, že kvůli shodě jmen bloků by byly přepsány z jiné šablonoy.

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

Vykreslení bloků {include}

Chcete-li blok vypsat na určitém místě, použijte značku {include blockname}:

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

<h1>{include title}</h1>

Značka {include} se také používá k vkládání celých šablon nebo k vykreslení jednoho bloku z jiné šablony:

{include footer from 'main.latte'}

Vykreslovaný blok nemá přístup k proměnným aktivního kontextu, kromě 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 předávat tímto způsobem:

{* od Latte 2.9 *}
{include footer, foo: bar, id: 123}

{* před Latte 2.9 *}
{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 ještě doplníme klíčové slovo block, aby už v době kompilace Latte vědělo, že jde blok, a nikoliv 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 například užitečné 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, ...} pak můžeme napsat {include this, ...}, kde this znamená aktuální blok.

Vykreslovaný blok lze upravit pomocí filtrů. Následující příklad odebere všechna HTML 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 jen 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 bychom je přirovnali k funkcím. Jsou užitečné k opětovnému použití fragmentů šablony, abyste se neopakovali.

Latte se snaží dělat věci jednoduše, takže v zásadě jsou definice stejné jako bloky a všechno, co je o blocích řečeno, platí také pro definice. Liší se od bloků pouze třemi způsoby:

  1. mohou přijímat argumenty
  2. nemohou mít filtry
  3. jsou uzavřeny ve značkách {define} a obsah uvnitř těchto značek se neodesílá na výstup, dokud je nevložíte. Díky tomu je můžete vytvořit kdekoli:
{block foo}<p>Hello</p>{/block}
{* prints: <p>Hello</p> *}

{define bar}<p>World</p>{/define}
{* prints nothing *}

{include bar}
{* prints: <p>World</p> *}

Představte si, že máte obecnou pomocnou šablonu, která definuje, jak vykreslit formuláře HTML pomocí definic:

{* forms.latte *}
{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 výchozí hodnota (zde text je výchozí hodnota pro $type, funguje od Latte 2.9.1). Od Latte 2.7 lze deklarovat také typy parametrů: {define input, string $name, ...}.

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

Vkládají se stejným způsobem jako blok:

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

Dynamické názvy bloků

Latte dovoluje 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 Peter, John a Mary:

{* parent.latte *}
{foreach [Peter, John, Mary] as $name}
	{block $name}Hi, I am {$name}.{/block}
{/foreach}

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

{* child.latte *}
{block John}Hello. I am {$name}.{/block}

Takže výstup bude vypadat takto:

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

Kontrola existence bloků {ifset}

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

{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 ještě doplníme klíčové slovo block, aby bylo jasné, že nejde o test existence proměnných:

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

Tipy

Několik tipů pro 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 zjistit, které značky bloku se zaví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ů jako <h1 n:block=title>Welcome to my awesome homepage</h1>
  • Bloky lze také použít bez názvů pouze k použití filtrů{block|strip} hello {/block}

Horizontální znovupoužití {import}

Horizontální znovupoužití je v Latte třetím mechanismem opětovné použitelnosti a dědičnosti. Umožňuje načíst bloky z jiných šablon. Je to podobné jako vytvoření souboru PHP s pomocnými funkcemi.

I když je layoutová dědičnost šablony jednou z nejmocnějších funkcí Latte, je omezena na jednoduchou dědičnost; Šablona může rozšířit pouze jednu další šablonu. Díky tomuto omezení je layoutová dědičnost snadno srozumitelná a snadno laditelná:

{layout 'layout.latte'}

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

Horizontální znovupoužití je způsob, jak dosáhnout vícenásobné dědičnosti, ale bez složitosti:

{layout 'layout.latte'}

{import 'blocks.latte'}

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

Příkaz {import} říká Latte, aby importoval všechny bloky a definice definované v blocks.latte do aktuální šablony.

{* blocks.latte *}

{block sidebar}...{/block}

V tomto příkladu příkaz {import} importuje blok sidebar do hlavní šablony.

Importovaná šablona nesmí rozšiřovat další šablonu a její tělo by mělo být prázdné. Importovaná šablona však může 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 chcete. Pokud dvě importované šablony definují stejný blok, vyhrává první z nich. Nejvyšší prioritu má ale hlavní šablona, která může přepsat jakýkoli importovaný blok.

Ke všem přepsaným blokům se dá postupně dostat tím že je vložíme jako vložen jak rodičovský blok:

{layout 'base.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 myšlenku 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 kostry pro menší jednotky obsahu a znovu je používat kdekoli chcete.

V jednotkové dědičnosti je klíčem značka {embed}. Kombinuje chování {include} a {layout}. Umožňuje vložit obsah jiné šablony či bloku a volitelně předat proměnné, stejně jako v případě {include}. Umožňuje také přepsat libovolný blok definovaný uvnitř vložené šablony, jako při použití {layout}.

Například použijeme prvek akordeon. Podívejme se na kostru prvku uloženou 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 vyplnit. Ano, jako v případě nadřazené šablony v layoutové dědičnosti. Vidíte také proměnnou $modifierClass.

Pojďme použít náš prvek v šabloně. Tady přichází ke slovu {embed}. Jedná se o mimořádně výkonnou značku, která nám umožňuje dělat všechny věci: vložit obsah šablony prvku, přidat do něj proměnné a přidat do něj 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 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} {* 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}

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

Pomocí {embed} lze vkládat nejen šablony, ale i jiné bloky, a tedy 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 větší srozumitelnost:

{include template}

Případ použití: Použití header.latte a footer.latte uvnitř layout.latte.

header.latte

<nav>
   <div>Homepage</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>Homepage</p>
{/block}

about.latte

{layout 'layout.latte'}

{block main}
	<p>About page</p>
{/block}

{import}

Případ použití: sidebar.latte v single.product.latte a 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}

Případ použití: Funkce, které 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, Submit, 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}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}