Sablon öröklődés és újrafelhasználhatóság

A sablonok újrafelhasználhatósága és az öröklési mechanizmusok azért vannak itt, hogy növeljék a termelékenységet, mivel minden sablon csak az egyedi tartalmát tartalmazza, és az ismétlődő elemek és struktúrák újrafelhasználásra kerülnek. Három fogalmat mutatunk be: az elrendezés öröklést, a horizontális újrafelhasználást és az egység öröklést.

A Latte sablon öröklés koncepciója hasonló a PHP osztály örökléshez. Meghatározunk egy szülő sablont, amelyből más gyermeksablonok kiterjeszthetők, és felülírhatják a szülő sablon egyes részeit. Nagyszerűen működik, ha az elemek közös szerkezetűek. Bonyolultnak hangzik? Ne aggódjon, nem az.

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

Nézzük meg az elrendezési sablon öröklődését egy példával kezdve. Ez egy szülő sablon, amelyet például layout.latte -nak fogunk hívni, és egy HTML vázdokumentumot definiál.

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

A {block} címkék három blokkot határoznak meg, amelyeket a gyermek sablonok kitölthetnek. A blokkcímke csak annyit tesz, hogy közli a sablonmotorral, hogy a gyermek sablonok felülbírálhatják a sablon ezen részeit a saját, azonos nevű blokkjuk meghatározásával.

Egy gyermek sablon így nézhet ki:

{layout 'layout.latte'}

{block title}My amazing blog{/block}

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

A {layout} címke itt a kulcs. Azt mondja a sablonmotornak, hogy ez a sablon egy másik sablont “bővít”. Amikor a Latte rendereli ezt a sablont, először megkeresi a szülő sablont – ebben az esetben a layout.latte.

Ekkor a sablonmotor észreveszi a layout.latte három blokkcímkét, és ezeket a blokkokat a gyermek sablon tartalmával helyettesíti. Vegye figyelembe, hogy mivel a gyermek sablon nem definiálta a footer blokkot, helyette a szülő sablon tartalmát használja. A szülő sablon {block} címkén belüli tartalom mindig tartalékként kerül felhasználásra.

A kimenet így nézhet ki:

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

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

{block content}
	<h1>{block title}Welcome to my awesome homepage{/block}</h1>
{/block}

A blokk mindig létrejön, függetlenül attól, hogy a környező {if} feltétel igaznak vagy hamisnak értékelődik-e ki. Ellentétben azzal, amit gondolhat, ez a sablon valóban definiál egy blokkot.

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

Ha azt szeretné, hogy a blokkban lévő 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 lévő blokkokon kívüli adatok a layout sablon megjelenítése előtt kerülnek végrehajtásra, így használhatja a {var $foo = bar} típusú változók definiálására és az adatok továbbítására az egész öröklési láncban:

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

...

Többszintű öröklés

Annyi szintű öröklést használhat, amennyire csak szüksége van. Az elrendezés öröklésének egyik gyakori 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ését.
  2. Hozzon létre egy layout-SECTIONNAME.latte sablont a webhely minden egyes szakaszához. Például: layout-news.latte, layout-blog.latte stb. Ezek a sablonok mind a layout.latte bővítik, és tartalmazzák a szekció-specifikus stílusokat/designt.
  3. Hozzon létre egyedi sablonokat minden egyes oldaltípushoz, például egy hírcikkhez vagy blogbejegyzéshez. Ezek a sablonok kiterjesztik a megfelelő szakaszsablont.

Dinamikus elrendezés öröklődése

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

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

A Latte API-t használhatja az elrendezési sablon automatikus kiválasztására is.

Tippek

Íme néhány tipp az elrendezés örökléssel való munkához:

  • Ha a {layout} címet használja egy sablonban, akkor annak a sablonban az első sabloncímkének kell lennie.
  • Az elrendezés automatikusan kereshető (mint az előadókban). Ebben az esetben, ha a sablonban nem kell elrendezés, akkor ezt a {layout none} címkével jelzi.
  • A {layout} címkének a {extends} aliasával rendelkezik.
  • A kiterjesztett sablon fájlneve a sablon betöltőjétől függ.
  • Annyi blokkja lehet, amennyit csak akar. Ne feledje, hogy a gyermek sablonoknak nem kell minden szülői blokkot definiálniuk, így számos blokkban kitölthet ésszerű alapértelmezéseket, és csak azokat definiálhatja, amelyekre később szüksége van.

Blokkok {block}

Lásd még anonim {block}

A blokk lehetőséget biztosít arra, hogy megváltoztassuk a sablon egy bizonyos részének megjelenítési módját, de semmilyen módon nem avatkozik bele a körülötte lévő logikába. A következő példával szemléltetjük, hogyan működik egy blokk, és ami még fontosabb, hogyan nem működik:

{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 lenne a blokkcímkékkel vagy azok nélkül. A blokkok hozzáférnek a külső hatókörök változóihoz. Ez csak egy módja annak, hogy egy gyermek sablon által felülbírálhatóvá tegye:

{layout 'parent.Latte'}

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

Most a gyermek sablon megjelenítésekor a ciklus a child.Latte gyermek sablonban definiált blokkot fogja használni a parent.Latte alapsablonban definiált helyett ; a végrehajtott sablon ekvivalens a következővel:

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

Ha azonban egy új változót hozunk létre egy megnevezett blokkban, vagy kicseréljük egy meglévő változó értékét, a változás csak a blokkban lesz látható:

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

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

A blokk tartalma szűrőkkel módosítható. A következő példa eltávolít minden HTML-t és címszavakat:

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

A címke n:attribútumként is leírható:

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

Helyi blokkok

Minden blokk felülírja az azonos nevű szülői blokk tartalmát. Kivéve a helyi blokkokat. Ezek olyanok, mint az osztály privát metódusai. Létrehozhatsz egy sablont anélkül, hogy aggódnod kellene, hogy – a blokknevek egybeesése miatt – a második sablon felülírja őket.

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

Blokkok nyomtatása {include}

Lásd még {include file}

Ha egy blokkot egy adott helyre szeretne kinyomtatni, használja a {include blockname} címkét:

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

<h1>{include title}</h1>

A blokkot egy másik sablonból is megjelenítheti:

{include footer from 'main.latte'}

A nyomtatott 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 szerepel. A globális változókhoz azonban hozzáférnek.

A változókat a következő módon adhatja át a blokknak:

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

A blokk neveként használhat egy változót vagy bármilyen PHP-kifejezést. Ebben az esetben a változó elé írja a block kulcsszót, hogy már fordításkor tudható legyen, hogy egy blokkról van szó, és ne egy insert template-ről, amelynek a neve szintén szerepelhetne a változóban:

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

A blokk önmagán belül is kiírható, ami hasznos például egy fa struktúra megjeleníté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 kinyomtatott tartalom szűrőkkel módosítható. A következő példa eltávolít minden HTML-t és címszavakat:

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

Szülői blokk

Ha ki kell nyomtatnia a blokk tartalmát a szülő sablonból, akkor a {include parent} utasítás megteszi a hatását. Ez akkor hasznos, ha a szülő blokk tartalmához szeretne hozzáadni ahelyett, hogy teljesen felülírná azt.

{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 “definíciók” is vannak. Ezek a hagyományos programozási nyelvek függvényeihez hasonlíthatók. Hasznosak a sablonrészletek újrafelhasználásához, hogy ne ismételje magát.

A Latte igyekszik a dolgokat egyszerűen tartani, ezért a definíciók alapvetően ugyanazok, mint a blokkok, és minden, amit a blokkokról mondunk, a definíciókra is vonatkozik. Abban különböznek a blokkoktól:

  1. {define} címkékkel vannak körülvéve
  2. csak akkor kerülnek megjelenítésre, amikor a következő módon kerülnek beillesztésre {include}
  3. paramétereket definiálhatsz számukra, mint a PHP függvényeknél.
{block foo}<p>Hello</p>{/block}
{* prints: <p>Hello</p> *}

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

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

Képzeljük el, hogy van egy segédsablonunk, amely a HTML űrlapok rajzolásának definícióit tartalmazza.

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

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

A definíciók argumentumai mindig opcionálisak, alapértelmezett értékük null, kivéve, ha alapértelmezett érték van megadva (itt a 'text' az alapértelmezett érték a $type számára). A paramétertípusok is deklarálhatók: {define input, string $name, ...}.

A definíciókat tartalmazó sablon betöltése a {import}. Maguk a definíciók ugyanúgy kerülnek megjelenítésre, 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 a globális változókhoz igen.

Dinamikus blokknevek

A Latte nagy rugalmasságot biztosít 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, amelyek neve hi-Peter, hi-John és hi-Mary:

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

Egy gyermek sablonban például csak egy blokkot definiálhatunk újra:

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

Így a kimenet így fog kinézni:

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

{ifset}

Lásd még {ifset $var}

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

{ifset footer}
	...
{/ifset}

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

A blokk neveként használhat egy változót vagy bármely PHP kifejezését. Ebben az esetben a változó elé írja be a block kulcsszót, hogy egyértelmű legyen, hogy nem a változót ellenőrzi:

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

A blokkok meglétét szintén a hasBlock():

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

Tippek

Íme néhány tipp a blokkokkal való munkához:

  • Az utolsó felső szintű blokknak nem kell záró taggel rendelkeznie (a blokk a dokumentum végével ér véget). Ez leegyszerűsíti a gyermek sablonok írását, amelyek egy elsődleges blokk.
  • Az extra olvashatóság érdekében opcionálisan nevet adhat a {/block} tagnek, például {/block footer}. A névnek azonban meg kell egyeznie a blokk nevével. Nagyobb sablonoknál ez a technika segít abban, hogy lássa, mely blokkcímkéket zárja le.
  • Egy sablonban közvetlenül nem definiálhat több azonos nevű blokkcímkét. Ez azonban dinamikus blokknevek használatával megvalósítható.
  • Az n:attribútumokat használhatja a blokkok definiálására, mint például <h1 n:block=title>Welcome to my awesome homepage</h1>
  • A blokkok nevek nélkül is használhatók, csak a szűrők kimenetre történő alkalmazására: {block|strip} hello {/block}

Vízszintes újrafelhasználás {import}

A horizontális újrafelhasználás a Latte harmadik újrafelhasználási és öröklési mechanizmusa. Lehetővé teszi a blokkok betöltését más sablonokból. Ez hasonló ahhoz, mintha PHP-ben létrehoznánk egy fájlt segédfüggvényekkel, majd betöltenénk a require segítségével.

Bár a sablonok elrendezésének öröklése a Latte egyik legerősebb funkciója, az egyszerű öröklé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és elérésének.

Legyen egy sor blokkdefiníció:

{block sidebar}...{/block}

{block menu}...{/block}

A {import} paranccsal importáljuk a blocks.latte alatt definiált összes blokkot és definíciót egy másik sablonba:

{import 'blocks.latte'}

{* oldalsáv és menü blokkok mostantól használhatók *}

Ha a blokkokat a szülő sablonba importálja (azaz a {import} parancsot használja a layout.latte), akkor a blokkok az összes gyermek sablonban is elérhetőek lesznek, ami nagyon praktikus.

Az importálni kívánt sablon (pl. blocks.latte) nem bővíthet ki egy másik sablont, azaz használja a {layout}. Más sablonokat azonban importálhat.

A {import} címkének kell lennie az első sabloncímkének a {layout} után. A sablon neve bármilyen PHP-kifejezés lehet:

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

Annyi {import} utasítást használhat egy adott sablonban, amennyit csak akar. Ha két importált sablon ugyanazt a blokkot definiálja, akkor az első nyer. A legnagyobb prioritást azonban a fő sablon kapja, amely bármelyik importált blokkot felülírhatja.

A felülírt blokkok tartalma megőrizhető a blokknak a szülő blokkhoz hasonló módon történő beillesztésével:

{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} helyesen hívja meg a sidebar blokkot a blocks.latte sablonból.

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

Az egység-öröklés az elrendezés öröklés gondolatát a tartalomrészletek szintjére viszi át. Míg az elrendezésöröklés “dokumentumvázakkal” dolgozik, amelyeket a gyermeksablonok hívnak életre, addig az egységöröklés lehetővé teszi, hogy kisebb tartalmi egységek vázait hozza létre, és azokat bárhol újra felhasználja.

Az egységöröklésben a {embed} címke a kulcs. Egyesíti a {include} és a {layout} viselkedését. Lehetővé teszi egy másik sablon vagy blokk tartalmának beépítését, és opcionálisan változók átadását, ahogyan a {include} is teszi. Azt is lehetővé teszi, hogy felülírja a bevont sablonon belül definiált blokkokat, mint a {layout}.

Példának okáért az összecsukható harmonika elemet fogjuk használni. Nézzük meg az elem vázát a collapsible.latte sablonban :

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

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

A {block} címkék két blokkot határoznak meg, amelyeket a gyermek sablonok tölthetnek ki. Igen, mint a szülő sablon esetében az elrendezés öröklés sablonban. A $modifierClass változót is láthatja.

Használjuk az elemünket a sablonban. Itt jön a képbe a {embed}. Ez egy szuper erős készlet, amivel mindent megtehetünk: bevonhatjuk az elem sablonjának tartalmát, hozzáadhatunk változókat, és hozzáadhatunk blokkokat egyéni HTML-ekkel:

{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ágyazási címkéken belüli blokkok a többi blokkoktól független, külön réteget alkotnak. Ezért lehet ugyanolyan nevük, mint a beágyazáson kívüli blokknak, és semmilyen módon nem befolyásolják őket. A {embed} címkéken belüli include tag használatával beilleszthetünk itt létrehozott blokkokat, a beágyazott sablonból származó blokkokat (amelyek nem lokálisak), és a fő sablonból származó blokkokat is, amelyek helyesek. Más fájlokból is importálhatsz 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 a beágyazáson belül van definiálva *}
		{include hello} {* működik, a blokk ebben a sablonban helyi *}
		{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 a globális változókhoz igen.

A {embed} segítségével nemcsak sablonokat, hanem más blokkokat is beilleszthetünk, így az előző példát így is meg 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 átadunk egy kifejezést a {embed} címre, és nem egyértelmű, hogy az egy blokk vagy fájlnév, akkor adjuk hozzá a block vagy a file kulcsszót:

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

Felhasználási esetek

A Latte-ban az öröklésnek és a kód újrafelhasználásának különböző típusai vannak. Foglaljuk össze a főbb fogalmakat a nagyobb áttekinthetőség érdekében:

{include template}

Használati eset: A header.latte & footer.latte használata a layout.latte oldalon belül.

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}

Használati eset: A layout.latte kiterjesztése a homepage.latte és a about.latte oldalakon belül.

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}

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

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}

Használati eset: Egy függvény, amely megkap néhány változót és kiad néhány jelölést.

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: A pagination.latte beágyazása a product.table.latte & 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}
verzió: 3.0