Sablonok öröklődése és újrafelhasználhatósága

A sablonok újrafelhasználási és öröklődési mechanizmusai növelik a termelékenységet, mivel minden sablon csak az egyedi tartalmát tartalmazza, az ismétlődő elemek és struktúrák pedig újrafelhasználhatók. Három koncepciót mutatunk be: elrendezés öröklődés, horizontális újrafelhasználás és egység öröklődés.

A Latte sablon öröklődési koncepciója hasonló a PHP osztályöröklődéséhez. Definiál egy szülő sablont, amelytől más gyermek sablonok örökölhetnek, és felülírhatják a szülő sablon részeit. Ez nagyszerűen működik, amikor az elemek közös struktúrán osztoznak. Bonyolultnak hangzik? Ne aggódjon, nagyon egyszerű.

Layout öröklődés {layout}

Nézzük meg a layout sablon öröklődését, azaz a layoutot, egy példával. Ez egy szülő sablon, amelyet például layout.latte-nak nevezünk, és amely meghatározza a HTML dokumentum vázát:

<!doctype html>
<html lang="hu">
<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>

A {block} tagek három blokkot definiálnak, amelyeket a gyermek sablonok kitölthetnek. A block tag csak annyit tesz, hogy jelzi, hogy ezt a helyet a gyermek sablon felülírhatja egy saját, azonos nevű blokk definiálásával.

Egy gyermek sablon így nézhet ki:

{layout 'layout.latte'}

{block title}Az én csodálatos blogom{/block}

{block content}
	<p>Üdvözöllek a fantasztikus honlapomon.</p>
{/block}

A kulcs itt a {layout} tag. Ez közli a Latte-val, hogy ez a sablon „kiterjeszt“ egy másik sablont. Amikor a Latte rendereli ezt a sablont, először megtalálja a szülő sablont – ebben az esetben a layout.latte-t.

Ezen a ponton a Latte észreveszi a három blokk taget a layout.latte-ban, és ezeket a blokkokat a gyermek sablon tartalmával helyettesíti. Mivel a gyermek sablon nem definiálta a footer blokkot, helyette a szülő sablon tartalma kerül felhasználásra. A {block} tag tartalma a szülő sablonban mindig tartalékként használatos.

A kimenet így nézhet ki:

<!doctype html>
<html lang="hu">
<head>
	<title>Az én csodálatos blogom</title>
	<link rel="stylesheet" href="style.css">
</head>
<body>
	<div id="content">
		<p>Üdvözöllek a fantasztikus honlapomon.</p>
	</div>
	<div id="footer">
		&copy; Copyright 2008
	</div>
</body>
</html>

A gyermek sablonban a blokkok csak a legfelső szinten vagy egy másik blokkon belül helyezhetők el, azaz:

{block content}
	<h1>{block title}Üdvözöllek a fantasztikus honlapomon{/block}</h1>
{/block}

Továbbá egy blokk mindig létrejön, függetlenül attól, hogy a környező {if} feltétel igazra vagy hamisra értékelődik-e ki. Tehát még ha nem is úgy tűnik, ez a sablon definiálja a blokkot.

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

Ha azt szeretné, hogy a blokkon belüli kimenet feltételesen jelenjen meg, használja helyette a következőt:

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

A gyermek sablonban a blokkokon kívüli tér a layout sablon renderelése előtt kerül végrehajtásra, így használhatja őket változók definiálására, mint {var $foo = bar}, és adatok terjesztésére az egész öröklődési láncon keresztül:

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

...

Többszintű öröklődés

Annyi öröklődési szintet használhat, amennyire szüksége van. A layout öröklődés gyakori használati módja a következő háromszintű megközelítés:

  1. Hozzon létre egy layout.latte sablont, amely tartalmazza a webhely fő megjelenési vázát.
  2. Hozzon létre egy layout-SECTIONNAME.latte sablont a webhely minden szakaszához. Például layout-news.latte, layout-blog.latte stb. Mindezek a sablonok kiterjesztik a layout.latte-t, és tartalmazzák az egyes szakaszokra jellemző stílusokat és dizájnt.
  3. Hozzon létre egyedi sablonokat minden oldaltípushoz, például egy hírcikkhez vagy blogbejegyzéshez. Ezek a sablonok a megfelelő szakasz sablonját terjesztik ki.

Dinamikus öröklődés

A szülő sablon neveként használható változó vagy bármilyen PHP kifejezés, így az öröklődés dinamikusan viselkedhet:

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

A Latte API-t is használhatja a layout sablon automatikus kiválasztásához.

Tippek

Íme néhány tipp a layout öröklődéssel való munkához:

  • Ha a sablonban {layout}-ot használ, annak a sablon első tagjének kell lennie.
  • A layout automatikusan kereshető (mint például a presenterekben). Ebben az esetben, ha a sablonnak nem kell layouttal rendelkeznie, ezt a {layout none} taggel jelzi.
  • A {layout} tagnek van egy aliasa: {extends}.
  • A layout fájl neve a loadertől függ.
  • Annyi blokkot hozhat létre, amennyit csak akar. Ne feledje, hogy a gyermek sablonoknak nem kell definiálniuk az összes szülő blokkot, így több blokkban is kitölthet ésszerű alapértelmezett értékeket, majd később csak azokat definiálhatja, amelyekre szüksége van.

Blokkok {block}

Lásd még az anonim {block}

A blokk egy módja annak, hogy megváltoztassuk egy sablonrész renderelésének módját, de semmilyen módon nem avatkozik be a körülötte lévő logikába. A következő példában bemutatjuk, hogyan működik a blokk, de azt is, hogyan nem:

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

Ha ezt a sablont rendereli, az eredmény pontosan ugyanaz lesz a {block} tagekkel és anélkül is. A blokkok hozzáférhetnek a külső hatókörök változóihoz. Csak lehetőséget adnak arra, hogy egy gyermek sablon felülírja őket:

{layout 'parent.latte'}

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

Most, amikor a gyermek sablont rendereljük, a ciklus a child.latte gyermek sablonban definiált blokkot fogja használni a parent.latte-ban definiált blokk helyett; a futtatott sablon ekkor a következővel egyenértékű:

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

Ha azonban egy nevesített blokkon belül új változót hozunk létre, vagy egy meglévő értékét helyettesítjük, a változás csak a blokkon belül lesz látható:

{var $foo = 'foo'}
{block post}
	{do $foo = 'új érték'}
	{var $bar = 'bar'}
{/block}

foo: {$foo}                  // kiírja: foo
bar: {$bar ?? 'nincs definiálva'} // kiírja: nincs definiálva

A blokk tartalmát szűrőkkel lehet módosítani. A következő példa eltávolítja az összes HTML-t és megváltoztatja a betűméretet:

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

A taget n:attributumként is írható:

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

Lokális blokkok

Minden blokk felülírja az azonos nevű szülő blokk tartalmát – kivéve a lokális blokkokat. Az osztályokban ez valami olyasmi lenne, mint a privát metódusok. Így anélkül hozhat létre sablont, hogy aggódnia kellene amiatt, hogy a blokknevek egyezése miatt más sablonból felülíródnának.

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

Blokkok renderelése {include}

Lásd még {include file}

Ha egy blokkot egy adott helyen szeretne kiírni, használja a {include blockname} taget:

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

<h1>{include title}</h1>

Lehetőség van egy másik sablonból származó blokk kiírására is:

{include footer from 'main.latte'}

A renderelt blokk nem fér hozzá az aktív kontextus változóihoz, kivéve, ha a blokk ugyanabban a fájlban van definiálva, ahol be van illesztve. Azonban hozzáfér a globális változókhoz.

Változókat így adhat át a blokknak:

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

A blokk neveként használható változó vagy bármilyen PHP kifejezés. Ebben az esetben a változó elé még hozzáadjuk a block kulcsszót, hogy már a fordításkor tudja a Latte, hogy ez egy blokk, és nem egy sablon beillesztése, amelynek a neve szintén lehet egy változóban:

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

A blokk önmagán belül is renderelhető, ami például hasznos egy fastruktúra renderelésekor:

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

A {include menu, ...} helyett írhatjuk azt is, hogy {include this, ...}, ahol a this az aktuális blokkot jelenti.

A renderelt blokkot szűrőkkel lehet módosítani. A következő példa eltávolítja az összes HTML-t és megváltoztatja a betűméretet:

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

Szülő blokk

Ha a szülő sablonból származó blokk tartalmát kell kiírnia, használja a {include parent}-et. Ez akkor hasznos, ha csak ki szeretné egészíteni a szülő blokk tartalmát ahelyett, hogy teljesen felülírná.

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

Definíciók {define}

A blokkok mellett a Latte-ban léteznek “definíciók” is. A hagyományos programozási nyelvekben ezeket a függvényekhez hasonlítanánk. Hasznosak a sablonrészletek újrafelhasználására, hogy ne ismételjük önmagunkat.

A Latte igyekszik egyszerűvé tenni a dolgokat, így alapvetően a definíciók ugyanazok, mint a blokkok, és minden, ami a blokkokról elmondható, a definíciókra is igaz. Abban különböznek a blokkoktól, hogy:

  1. {define} tagekbe vannak zárva
  2. csak akkor renderelődnek, ha {include}-dal beillesztjük őket
  3. paramétereket lehet nekik definiálni, hasonlóan a PHP függvényeihez
{block foo}<p>Hello</p>{/block}
{* kiírja: <p>Hello</p> *}

{define bar}<p>World</p>{/define}
{* nem ír ki semmit *}

{include bar}
{* kiírja: <p>World</p> *}

Képzeljen el egy segédsablont definíciók gyűjteményével, hogyan kell HTML űrlapokat rajzolni.

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

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

Az argumentumok mindig opcionálisak, alapértelmezett értékük null, hacsak nincs megadva alapértelmezett érték (itt a 'text' az alapértelmezett érték $type-hoz). A paraméterek típusait is deklarálhatjuk: {define input, string $name, ...}.

A definíciókat tartalmazó sablont {import} segítségével töltjük be. Maguk a definíciók ugyanúgy renderelődnek, mint a blokkok:

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

A definíciók nem férnek hozzá az aktív kontextus változóihoz, de hozzáférnek a globális változókhoz.

Dinamikus blokknevek

A Latte nagy rugalmasságot tesz lehetővé a blokkok definiálásában, mivel a blokk neve bármilyen PHP kifejezés lehet. Ez a példa három blokkot definiál hi-Peter, hi-John és hi-Mary néven:

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

A gyermek sablonban ezután például csak egy blokkot definiálhatunk újra:

{block hi-John}Hello. Én {$name} vagyok.{/block}

Így a kimenet így fog kinézni:

Szia, Peter vagyok.
Hello. Én John vagyok.
Szia, Mary vagyok.

Blokkok létezésének ellenőrzése {ifset}

Lásd még {ifset $var}

A {ifset blockname} teszt segítségével ellenőrizzük, hogy az aktuális kontextusban létezik-e egy blokk (vagy több blokk):

{ifset footer}
	...
{/ifset}

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

A blokk neveként használható változó vagy bármilyen PHP kifejezés. Ebben az esetben a változó elé még hozzáadjuk a block kulcsszót, hogy egyértelmű legyen, hogy nem a változók létezésének teszteléséről van szó:

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

A blokkok létezését a hasBlock() függvény is ellenőrzi:

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

Tippek

Néhány tipp a blokkokkal való munkához:

  • Az utolsó legfelső szintű blokknak nem kell záró taggel rendelkeznie (a blokk a dokumentum végével ér véget). Ez leegyszerűsíti az egy elsődleges blokkot tartalmazó gyermek sablonok írását.
  • A jobb olvashatóság érdekében megadhatja a blokk nevét a {/block} tagben, például {/block footer}. A névnek azonban meg kell egyeznie a blokk nevével. Nagyobb sablonokban ez a technika segít megállapítani, hogy mely blokk tagek záródnak.
  • Ugyanabban a sablonban nem definiálhat közvetlenül több azonos nevű blokk taget. Ezt azonban dinamikus blokknevekkel lehet elérni.
  • Használhat n:attribútumokat blokkok definiálására, mint <h1 n:block=title>Üdvözöllek a fantasztikus honlapomon</h1>
  • A blokkok név nélkül is használhatók, csak a szűrők alkalmazására: {block|strip} hello {/block}

Horizontális újrafelhasználás {import}

A horizontális újrafelhasználás a Latte-ban a harmadik mechanizmus az újrafelhasználásra és öröklődésre. Lehetővé teszi blokkok betöltését más sablonokból. Ez hasonló ahhoz, mint amikor PHP-ban létrehozunk egy fájlt segédfüggvényekkel, amelyet aztán a require segítségével töltünk be.

Bár a sablon layout öröklődése a Latte egyik legerősebb funkciója, egyszerű öröklődésre korlátozódik – egy sablon csak egy másik sablont terjeszthet ki. A horizontális újrafelhasználás egy módja a többszörös öröklődés elérésének.

Legyen egy fájlunk blokkdefiníciókkal:

{block sidebar}...{/block}

{block menu}...{/block}

Az {import} paranccsal importáljuk az összes blokkot és definíciót, amelyek a blocks.latte-ban vannak definiálva, egy másik sablonba:

{import 'blocks.latte'}

{* most már használhatók a sidebar és menu blokkok *}

Ha a blokkokat egy szülő sablonban importálja (azaz {import}-ot használ a layout.latte-ban), a blokkok minden gyermek sablonban is elérhetők lesznek, ami nagyon praktikus.

Az importálásra szánt sablon (pl. blocks.latte) nem terjeszthet ki másik sablont, azaz nem használhat {layout}-ot. Azonban importálhat más sablonokat.

Az {import} tagnek a {layout} utáni első sablon tagjének kell lennie. A sablon neve bármilyen PHP kifejezés lehet:

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

A sablonban annyi {import} parancsot használhat, amennyit csak akar. Ha két importált sablon ugyanazt a blokkot definiálja, az első nyer. A legmagasabb prioritása azonban a fő sablonnak van, amely felülírhat bármilyen importált blokkot.

A felülírt blokkok tartalmát megőrizhetjük úgy, hogy a blokkot ugyanúgy illesztjük be, mint a szülő blokkot:

{layout 'layout.latte'}

{import 'blocks.latte'}

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

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

Ebben a példában a {include parent} a sidebar blokkot hívja meg a blocks.latte sablonból.

Egység öröklődés {embed}

Az egység öröklődés kiterjeszti a layout öröklődés gondolatát a tartalomfragmentumok szintjére. Míg a layout öröklődés a „dokumentumvázakkal“ dolgozik, amelyeket a gyermek sablonok keltenek életre, az egység öröklődés lehetővé teszi kisebb tartalmi egységek vázainak létrehozását és újrafelhasználását bárhol, ahol csak akarja.

Az egység öröklődésben a kulcs a {embed} tag. Kombinálja az {include} és a {layout} viselkedését. Lehetővé teszi egy másik sablon vagy blokk tartalmának beágyazását, és opcionálisan változók átadását, ugyanúgy, mint az {include} esetében. Lehetővé teszi továbbá a beágyazott sablonon belül definiált bármely blokk felülírását, mint a {layout} használatakor.

Például használjunk egy harmonika elemet. Nézzük meg az elem vázát, amely a collapsible.latte sablonban van tárolva:

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

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

A {block} tagek két blokkot definiálnak, amelyeket a gyermek sablonok kitölthetnek. Igen, mint a layout öröklődés szülő sablonja esetében. Láthatja a $modifierClass változót is.

Használjuk az elemünket egy sablonban. Itt jön képbe az {embed}. Ez egy rendkívül erőteljes tag, amely lehetővé teszi számunkra, hogy minden dolgot megtegyünk: beágyazzuk az elem sablonjának tartalmát, változókat adjunk hozzá, és blokkokat adjunk hozzá saját HTML-lel:

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

A kimenet így nézhet ki:

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

A beágyazott tageken belüli blokkok különálló réteget alkotnak, amely független a többi blokktól. Ezért lehet ugyanaz a nevük, mint a beágyazáson kívüli blokkoknak, és semmilyen módon nem befolyásolják őket. Az include tag használatával az {embed} tageken belül beillesztheti az itt létrehozott blokkokat, a beágyazott sablonból származó blokkokat (amelyek nem lokálisak), valamint a fő sablonból származó blokkokat, amelyek viszont lokálisak. Más fájlokból is importálhat blokkokat:

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

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

	{block inner}…{/block}

	{block title}
		{include inner} {* működik, a blokk az embed-en belül van definiálva *}
		{include hello} {* működik, a blokk lokális ebben a sablonban *}
		{include content} {* működik, a blokk a beágyazott sablonban van definiálva *}
		{include aBlockDefinedInImportedTemplate} {* működik *}
		{include outer} {* nem működik! - a blokk a külső rétegben van *}
	{/block}
{/embed}

A beágyazott sablonok nem férnek hozzá az aktív kontextus változóihoz, de hozzáférnek a globális változókhoz.

Az {embed} segítségével nemcsak sablonokat, hanem más blokkokat is be lehet illeszteni, így az előző példát így is le lehetne írni:

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

Ha az {embed}-be egy kifejezést adunk át, és nem egyértelmű, hogy ez egy blokk vagy egy fájl neve, akkor hozzáadjuk a block vagy file kulcsszót:

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

Használati esetek

A Latte-ban különböző típusú öröklődés és kód újrafelhasználás létezik. Foglaljuk össze a fő koncepciókat a nagyobb érthetőség kedvéért:

{include template}

Használati eset: header.latte és footer.latte használata a layout.latte-on belül.

header.latte

<nav>
   <div>Kezdőlap</div>
   <div>Rólunk</div>
</nav>

footer.latte

<footer>
   <div>Copyright</div>
</footer>

layout.latte

{include 'header.latte'}

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

{include 'footer.latte'}

{layout}

Használati eset: layout.latte kiterjesztése a homepage.latte és about.latte fájlokban.

layout.latte

{include 'header.latte'}

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

{include 'footer.latte'}

homepage.latte

{layout 'layout.latte'}

{block main}
	<p>Kezdőlap</p>
{/block}

about.latte

{layout 'layout.latte'}

{block main}
	<p>Rólunk oldal</p>
{/block}

{import}

Használati eset: sidebar.latte a single.product.latte és single.service.latte fájlokban.

sidebar.latte

{block sidebar}<aside>Ez az oldalsáv</aside>{/block}

single.product.latte

{layout 'product.layout.latte'}

{import 'sidebar.latte'}

{block main}<main>Termék oldal</main>{/block}

single.service.latte

{layout 'service.layout.latte'}

{import 'sidebar.latte'}

{block main}<main>Szolgáltatás oldal</main>{/block}

{define}

Használati eset: Függvények, amelyeknek változókat adunk át, és valamit renderelnek.

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}

Használati eset: pagination.latte beágyazása a product.table.latte és service.table.latte fájlokba.

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}Első termék oldal{/block}
	{block last}Utolsó termék oldal{/block}
{/embed}

service.table.latte

{embed 'pagination.latte', min: 1, max: $services->count}
	{block first}Első szolgáltatás oldal{/block}
	{block last}Utolsó szolgáltatás oldal{/block}
{/embed}
verzió: 3.0