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 nadřazenou šablonu – 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ě.
  • Layout se může dohledávat automaticky (jako třeba 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 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}

Viz také anonymní {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:

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

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

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

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

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é šablony.

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

Vykreslení bloků {include}

Viz také {include file}

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>

Lze také vypsat blok 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 do bloku 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ů tím, že:

  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}
{* prints: <p>Hello</p> *}

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

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

Představte si, že máte pomocnou šablonu s kolekcí definic, jak kreslit formuláře 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}

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, ...}.

Šablonu s definicemi nahrajeme 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 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 hi-Peter, hi-John a hi-Mary:

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

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

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

Viz také {ifset $var}

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. 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čená 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 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 '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 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>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>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}
verze: 3.0 2.x