Template-Vererbung und Wiederverwendbarkeit
Mechanismen zur Wiederverwendung und Vererbung von Templates steigern Ihre Produktivität, da jedes Template nur seinen einzigartigen Inhalt enthält und wiederholte Elemente und Strukturen wiederverwendet werden. Wir stellen drei Konzepte vor: layoutová dědičnost, horizontální znovupoužití und jednotková dědičnost.
Das Konzept der Template-Vererbung in Latte ähnelt der Klassenvererbung in PHP. Sie definieren ein Eltern-Template, von dem andere Kind-Templates erben und Teile des Eltern-Templates überschreiben können. Dies funktioniert hervorragend, wenn Elemente eine gemeinsame Struktur teilen. Klingt kompliziert? Keine Sorge, es ist sehr einfach.
Layout-Vererbung {layout}
Schauen wir uns die Vererbung des Template-Layouts direkt an einem Beispiel an. Dies ist das Eltern-Template, das wir
beispielsweise layout.latte
nennen und das die Grundstruktur eines HTML-Dokuments definiert:
<!doctype html>
<html lang="de">
<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}© Copyright 2008{/block}
</div>
</body>
</html>
Die {block}
-Tags definieren drei Blöcke, die von Kind-Templates gefüllt werden können. Das
block
-Tag teilt lediglich mit, dass dieser Bereich von einem Kind-Template durch Definieren eines eigenen Blocks mit
demselben Namen überschrieben werden kann.
Ein Kind-Template könnte so aussehen:
{layout 'layout.latte'}
{block title}Mein erstaunlicher Blog{/block}
{block content}
<p>Willkommen auf meiner tollen Homepage.</p>
{/block}
Der Schlüssel hier ist das {layout}
-Tag. Es teilt Latte mit, dass dieses Template ein anderes Template
„erweitert“. Wenn Latte dieses Template rendert, findet es zuerst das Eltern-Template – in diesem Fall
layout.latte
.
An diesem Punkt bemerkt Latte die drei Block-Tags in layout.latte
und ersetzt diese Blöcke durch den Inhalt des
Kind-Templates. Da das Kind-Template den footer-Block nicht definiert hat, wird stattdessen der Inhalt aus dem
Eltern-Template verwendet. Der Inhalt innerhalb des {block}
-Tags im Eltern-Template wird immer als Fallback
verwendet.
Die Ausgabe könnte so aussehen:
<!doctype html>
<html lang="de">
<head>
<title>Mein erstaunlicher Blog</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="content">
<p>Willkommen auf meiner tollen Homepage.</p>
</div>
<div id="footer">
© Copyright 2008
</div>
</body>
</html>
Im Kind-Template können Blöcke nur auf der obersten Ebene oder innerhalb eines anderen Blocks platziert werden, d. h.:
{block content}
<h1>{block title}Willkommen auf meiner tollen Homepage{/block}</h1>
{/block}
Außerdem wird ein Block immer erstellt, unabhängig davon, ob die umgebende {if}
-Bedingung als wahr oder falsch
ausgewertet wird. Auch wenn es nicht so aussieht, definiert dieses Template den Block.
{if false}
{block head}
<meta name="robots" content="noindex, follow">
{/block}
{/if}
Wenn Sie möchten, dass die Ausgabe innerhalb des Blocks bedingt angezeigt wird, verwenden Sie stattdessen Folgendes:
{block head}
{if $condition}
<meta name="robots" content="noindex, follow">
{/if}
{/block}
Der Bereich außerhalb der Blöcke im Kind-Template wird vor dem Rendern des Layout-Templates ausgeführt, sodass Sie ihn
verwenden können, um Variablen wie {var $foo = bar}
zu definieren und Daten über die gesamte Vererbungskette zu
verbreiten:
{layout 'layout.latte'}
{var $robots = noindex}
...
Mehrstufige Vererbung
Sie können beliebig viele Vererbungsebenen verwenden. Eine gängige Methode zur Verwendung der Layout-Vererbung ist der folgende dreistufige Ansatz:
- Erstellen Sie ein Template
layout.latte
, das die Hauptstruktur des Website-Layouts enthält. - Erstellen Sie für jeden Abschnitt Ihrer Website ein Template
layout-SECTIONNAME.latte
. Zum Beispiellayout-news.latte
,layout-blog.latte
usw. Alle diese Templates erweiternlayout.latte
und enthalten Stile und Design, die spezifisch für die einzelnen Abschnitte sind. - Erstellen Sie individuelle Templates für jeden Seitentyp, z. B. einen Nachrichtenartikel oder einen Blogbeitrag. Diese Templates erweitern das entsprechende Abschnitts-Template.
Dynamische Vererbung
Als Name des Eltern-Templates kann eine Variable oder ein beliebiger PHP-Ausdruck verwendet werden, sodass sich die Vererbung dynamisch verhalten kann:
{layout $standalone ? 'minimum.latte' : 'layout.latte'}
Sie können auch die Latte API verwenden, um das Layout-Template automatisch auszuwählen.
Tipps
Hier sind einige Tipps für die Arbeit mit der Layout-Vererbung:
- Wenn Sie
{layout}
in einem Template verwenden, muss es das erste Template-Tag in diesem Template sein. - Das Layout kann automatisch gesucht
werden (wie z. B. in Presentern). Wenn in
diesem Fall das Template kein Layout haben soll, teilt es dies mit dem Tag
{layout none}
mit. - Das Tag
{layout}
hat den Alias{extends}
. - Der Dateiname des Layouts hängt vom Loader ab.
- Sie können beliebig viele Blöcke haben. Denken Sie daran, dass Kind-Templates nicht alle Eltern-Blöcke definieren müssen, sodass Sie in mehreren Blöcken angemessene Standardwerte eintragen und später nur die definieren können, die Sie benötigen.
Blöcke {block}
Siehe auch anonymer {block}
Ein Block ist eine Möglichkeit, die Art und Weise zu ändern, wie ein bestimmter Teil eines Templates gerendert wird, greift jedoch nicht in die Logik um ihn herum ein. Im folgenden Beispiel zeigen wir, wie ein Block funktioniert, aber auch, wie er nicht funktioniert:
{foreach $posts as $post}
{block post}
<h1>{$post->title}</h1>
<p>{$post->body}</p>
{/block}
{/foreach}
Wenn Sie dieses Template rendern, ist das Ergebnis mit und ohne {block}
-Tags genau dasselbe. Blöcke haben Zugriff
auf Variablen aus äußeren Bereichen. Sie geben lediglich die Möglichkeit, von einem Kind-Template überschrieben zu werden:
{layout 'parent.Latte'}
{block post}
<article>
<header>{$post->title}</header>
<section>{$post->text}</section>
</article>
{/block}
Beim Rendern des Kind-Templates verwendet die Schleife nun den im Kind-Template child.Latte
definierten Block
anstelle des in parent.Latte
definierten Blocks; das ausgeführte Template entspricht dann dem folgenden:
{foreach $posts as $post}
<article>
<header>{$post->title}</header>
<section>{$post->text}</section>
</article>
{/foreach}
Wenn wir jedoch eine neue Variable innerhalb eines benannten Blocks erstellen oder den Wert einer vorhandenen ersetzen, ist die Änderung nur innerhalb des Blocks sichtbar:
{var $foo = 'foo'}
{block post}
{do $foo = 'new value'}
{var $bar = 'bar'}
{/block}
foo: {$foo} // gibt aus: foo
bar: {$bar ?? 'not defined'} // gibt aus: not defined
Der Inhalt eines Blocks kann mithilfe von Filtern modifiziert werden. Das folgende Beispiel entfernt sämtliches HTML und ändert die Groß-/Kleinschreibung:
<title>{block title|stripHtml|capitalize}...{/block}</title>
Das Tag kann auch als n:Attribut geschrieben werden:
<article n:block=post>
...
</article>
Lokale Blöcke
Jeder Block überschreibt den Inhalt des Eltern-Blocks mit demselben Namen – außer lokale Blöcke. In Klassen wären dies so etwas wie private Methoden. Sie können also ein Template erstellen, ohne befürchten zu müssen, dass aufgrund übereinstimmender Blocknamen Blöcke aus einem anderen Template überschrieben werden.
{block local helper}
...
{/block}
Rendern von Blöcken {include}
Siehe auch {include file}
Um einen Block an einer bestimmten Stelle auszugeben, verwenden Sie das Tag {include blockname}
:
<title>{block title}{/block}</title>
<h1>{include title}</h1>
Es ist auch möglich, einen Block aus einem anderen Template auszugeben:
{include footer from 'main.latte'}
Der gerenderte Block hat keinen Zugriff auf die Variablen des aktiven Kontexts, außer wenn der Block in derselben Datei definiert ist, in der er auch eingefügt wird. Er hat jedoch Zugriff auf globale Variablen.
Sie können Variablen auf diese Weise an den Block übergeben:
{include footer, foo: bar, id: 123}
Als Blockname kann eine Variable oder ein beliebiger PHP-Ausdruck verwendet werden. In diesem Fall fügen wir vor der Variablen
noch das Schlüsselwort block
hinzu, damit Latte bereits zur Kompilierzeit weiß, dass es sich um einen Block handelt
und nicht um das Einfügen eines Templates, dessen Name ebenfalls in
einer Variablen stehen könnte:
{var $name = footer}
{include block $name}
Ein Block kann auch innerhalb seiner selbst gerendert werden, was beispielsweise beim Rendern einer Baumstruktur nützlich ist:
{define menu, $items}
<ul>
{foreach $items as $item}
<li>
{if is_array($item)}
{include menu, $item}
{else}
{$item}
{/if}
</li>
{/foreach}
</ul>
{/define}
Anstelle von {include menu, ...}
können wir dann {include this, ...}
schreiben, wobei
this
den aktuellen Block bedeutet.
Der gerenderte Block kann mithilfe von Filtern modifiziert werden. Das folgende Beispiel entfernt sämtliches HTML und ändert die Groß-/Kleinschreibung:
<title>{include heading|stripHtml|capitalize}</title>
Eltern-Block
Wenn Sie den Inhalt eines Blocks aus dem Eltern-Template ausgeben müssen, verwenden Sie {include parent}
. Dies
ist nützlich, wenn Sie den Inhalt des Eltern-Blocks nur ergänzen möchten, anstatt ihn vollständig zu überschreiben.
{block footer}
{include parent}
<a href="https://github.com/nette">GitHub</a>
<a href="https://twitter.com/nettefw">Twitter</a>
{/block}
Definition {define}
Neben Blöcken gibt es in Latte auch „Definitionen“. In gängigen Programmiersprachen würden wir sie mit Funktionen vergleichen. Sie sind nützlich zur Wiederverwendung von Template-Fragmenten, um Wiederholungen zu vermeiden.
Latte versucht, die Dinge einfach zu halten, daher sind Definitionen im Grunde genommen dasselbe wie Blöcke, und alles, was über Blöcke gesagt wird, gilt auch für Definitionen. Sie unterscheiden sich von Blöcken dadurch, dass:
- sie in
{define}
-Tags eingeschlossen sind - sie erst gerendert werden, wenn sie über
{include}
eingefügt werden - ihnen Parameter ähnlich wie Funktionen in PHP definiert werden können
{block foo}<p>Hallo</p>{/block}
{* gibt aus: <p>Hallo</p> *}
{define bar}<p>Welt</p>{/define}
{* gibt nichts aus *}
{include bar}
{* gibt aus: <p>Welt</p> *}
Stellen Sie sich vor, Sie haben ein Hilfs-Template mit einer Sammlung von Definitionen, wie HTML-Formulare gezeichnet werden sollen.
{define input, $name, $value, $type = 'text'}
<input type={$type} name={$name} value={$value}>
{/define}
{define textarea, $name, $value}
<textarea name={$name}>{$value}</textarea>
{/define}
Argumente sind immer optional mit dem Standardwert null
, sofern kein Standardwert angegeben ist (hier ist
'text'
der Standardwert für $type
). Es können auch Typen für Parameter deklariert werden:
{define input, string $name, ...}
.
Das Template mit Definitionen laden wir mit {import}
. Die
Definitionen selbst werden auf die gleiche Weise wie Blöcke gerendert:
<p>{include input, 'password', null, 'password'}</p>
<p>{include textarea, 'comment'}</p>
Definitionen haben keinen Zugriff auf die Variablen des aktiven Kontexts, aber sie haben Zugriff auf globale Variablen.
Dynamische Blocknamen
Latte ermöglicht große Flexibilität bei der Definition von Blöcken, da der Blockname ein beliebiger PHP-Ausdruck sein kann.
Dieses Beispiel definiert drei Blöcke mit den Namen hi-Peter
, hi-John
und hi-Mary
:
{foreach [Peter, John, Mary] as $name}
{block "hi-$name"}Hi, ich bin {$name}.{/block}
{/foreach}
Im Kind-Template können wir dann beispielsweise nur einen Block neu definieren:
{block hi-John}Hallo. Ich bin {$name}.{/block}
Die Ausgabe sieht dann so aus:
Hi, ich bin Peter.
Hallo. Ich bin John.
Hi, ich bin Mary.
Existenzprüfung von Blöcken {ifset}
Siehe auch {ifset $var}
Mit dem Test {ifset blockname}
prüfen wir, ob im aktuellen Kontext ein Block (oder mehrere Blöcke)
existiert:
{ifset footer}
...
{/ifset}
{ifset footer, header, main}
...
{/ifset}
Als Blockname kann eine Variable oder ein beliebiger PHP-Ausdruck verwendet werden. In diesem Fall fügen wir vor der Variablen
noch das Schlüsselwort block
hinzu, um deutlich zu machen, dass es sich nicht um einen Test auf Existenz von Variablen handelt:
{ifset block $name}
...
{/ifset}
Die Existenz von Blöcken prüft auch die Funktion hasBlock()
:
{if hasBlock(header) || hasBlock(footer)}
...
{/if}
Tipps
Einige Tipps zur Arbeit mit Blöcken:
- Der letzte Block auf der obersten Ebene muss kein schließendes Tag haben (der Block endet mit dem Ende des Dokuments). Dies vereinfacht das Schreiben von Kind-Templates, die einen primären Block enthalten.
- Zur besseren Lesbarkeit können Sie den Blocknamen im
{/block}
-Tag angeben, z. B.{/block footer}
. Der Name muss jedoch mit dem Blocknamen übereinstimmen. In größeren Templates hilft Ihnen diese Technik zu erkennen, welche Block-Tags geschlossen werden. - Sie können nicht direkt mehrere Block-Tags mit demselben Namen im selben Template definieren. Dies kann jedoch mithilfe von dynamischen Blocknamen erreicht werden.
- Sie können n:Attribute verwenden, um Blöcke wie
<h1 n:block=title>Willkommen auf meiner tollen Homepage</h1>
zu definieren. - Blöcke können auch ohne Namen verwendet werden, nur um Filter
anzuwenden:
{block|strip} hallo {/block}
Horizontale Wiederverwendung {import}
Horizontale Wiederverwendung ist in Latte der dritte Mechanismus zur Wiederverwendung und Vererbung. Sie ermöglicht das Laden
von Blöcken aus anderen Templates. Dies ähnelt dem Erstellen einer Datei mit Hilfsfunktionen in PHP, die dann mit
require
geladen wird.
Obwohl die Layout-Vererbung von Templates eine der mächtigsten Funktionen von Latte ist, ist sie auf einfache Vererbung beschränkt – ein Template kann nur ein anderes Template erweitern. Horizontale Wiederverwendung ist eine Möglichkeit, Mehrfachvererbung zu erreichen.
Nehmen wir eine Datei mit Blockdefinitionen:
{block sidebar}...{/block}
{block menu}...{/block}
Mit dem Befehl {import}
importieren wir alle in blocks.latte
definierten Blöcke und definice in ein anderes Template:
{import 'blocks.latte'}
{* jetzt können die Blöcke sidebar und menu verwendet werden *}
Wenn Sie Blöcke im Eltern-Template importieren (d. h. {import}
in layout.latte
verwenden), stehen
die Blöcke auch in allen Kind-Templates zur Verfügung, was sehr praktisch ist.
Das Template, das zum Importieren bestimmt ist (z. B. blocks.latte
), darf kein anderes Template erweitern, d. h. {layout}
verwenden. Es kann jedoch andere Templates
importieren.
Das {import}
-Tag sollte das erste Template-Tag nach {layout}
sein. Der Template-Name kann ein
beliebiger PHP-Ausdruck sein:
{import $ajax ? 'ajax.latte' : 'not-ajax.latte'}
Sie können in einem Template beliebig viele {import}
-Befehle verwenden. Wenn zwei importierte Templates denselben
Block definieren, gewinnt der erste. Die höchste Priorität hat jedoch das Haupt-Template, das jeden importierten Block
überschreiben kann.
Der Inhalt überschriebener Blöcke kann beibehalten werden, indem der Block genauso eingefügt wird, wie der rodičovský blok eingefügt wird:
{layout 'layout.latte'}
{import 'blocks.latte'}
{block sidebar}
{include parent}
{/block}
{block title}...{/block}
{block content}...{/block}
In diesem Beispiel ruft {include parent}
den sidebar
-Block aus dem Template
blocks.latte
auf.
Einheiten-Vererbung {embed}
Die Einheiten-Vererbung erweitert die Idee der Layout-Vererbung auf die Ebene von Inhaltsfragmenten. Während die Layout-Vererbung mit der „Dokumentstruktur“ arbeitet, die von Kind-Templates belebt wird, ermöglicht Ihnen die Einheiten-Vererbung, Strukturen für kleinere Inhaltseinheiten zu erstellen und sie überall wiederzuverwenden.
Bei der Einheiten-Vererbung ist das {embed}
-Tag der Schlüssel. Es kombiniert das Verhalten von
{include}
und {layout}
. Es ermöglicht das Einbetten des Inhalts eines anderen Templates oder Blocks und
optional das Übergeben von Variablen, genau wie bei {include}
. Es ermöglicht auch das Überschreiben jedes Blocks,
der innerhalb des eingebetteten Templates definiert ist, wie bei der Verwendung von {layout}
.
Verwenden wir beispielsweise ein Akkordeon-Element. Schauen wir uns die Struktur des Elements an, die im Template
collapsible.latte
gespeichert ist:
<section class="collapsible {$modifierClass}">
<h4 class="collapsible__title">
{block title}{/block}
</h4>
<div class="collapsible__content">
{block content}{/block}
</div>
</section>
Die {block}
-Tags definieren zwei Blöcke, die von Kind-Templates gefüllt werden können. Ja, wie im Fall des
Eltern-Templates bei der Layout-Vererbung. Sie sehen auch die Variable $modifierClass
.
Verwenden wir unser Element in einem Template. Hier kommt {embed}
ins Spiel. Es ist ein außerordentlich
mächtiges Tag, das uns erlaubt, alles zu tun: den Inhalt des Element-Templates einzubetten, Variablen hinzuzufügen und Blöcke
mit eigenem HTML hinzuzufügen:
{embed 'collapsible.latte', modifierClass: my-style}
{block title}
Hallo Welt
{/block}
{block content}
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing
elit. Nunc dapibus tortor vel mi dapibus sollicitudin.</p>
{/block}
{/embed}
Die Ausgabe könnte so aussehen:
<section class="collapsible my-style">
<h4 class="collapsible__title">
Hallo Welt
</h4>
<div class="collapsible__content">
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing
elit. Nunc dapibus tortor vel mi dapibus sollicitudin.</p>
</div>
</section>
Blöcke innerhalb eingebetteter Tags bilden eine separate Schicht, die unabhängig von anderen Blöcken ist. Daher können sie
denselben Namen wie ein Block außerhalb der Einbettung haben und werden davon nicht beeinflusst. Mit dem Tag include innerhalb der {embed}
-Tags können Sie hier erstellte Blöcke, Blöcke aus
dem eingebetteten Template (die nicht lokal sind) und auch Blöcke aus dem Haupt-Template
einfügen, die wiederum lokal sind. Sie können auch Blöcke aus anderen
Dateien importieren:
{block outer}…{/block}
{block local hello}…{/block}
{embed 'collapsible.latte', modifierClass: my-style}
{import 'blocks.latte'}
{block inner}…{/block}
{block title}
{include inner} {* funktioniert, Block ist innerhalb von embed definiert *}
{include hello} {* funktioniert, Block ist lokal in diesem Template *}
{include content} {* funktioniert, Block ist im eingebetteten Template definiert *}
{include aBlockDefinedInImportedTemplate} {* funktioniert *}
{include outer} {* funktioniert nicht! - Block befindet sich in der äußeren Schicht *}
{/block}
{/embed}
Eingebettete Templates haben keinen Zugriff auf die Variablen des aktiven Kontexts, aber sie haben Zugriff auf globale Variablen.
Mit {embed}
können nicht nur Templates, sondern auch andere Blöcke eingebettet werden, sodass das vorherige
Beispiel auf diese Weise geschrieben werden könnte:
{define collapsible}
<section class="collapsible {$modifierClass}">
<h4 class="collapsible__title">
{block title}{/block}
</h4>
...
</section>
{/define}
{embed collapsible, modifierClass: my-style}
{block title}
Hallo Welt
{/block}
...
{/embed}
Wenn wir an {embed}
einen Ausdruck übergeben und nicht klar ist, ob es sich um den Namen eines Blocks oder einer
Datei handelt, ergänzen wir das Schlüsselwort block
oder file
:
{embed block $name} ... {/embed}
Anwendungsfälle
In Latte gibt es verschiedene Arten der Vererbung und Wiederverwendung von Code. Fassen wir die Hauptkonzepte zur besseren Verständlichkeit zusammen:
{include template}
Anwendungsfall: Verwendung von header.latte
und footer.latte
innerhalb von
layout.latte
.
header.latte
<nav>
<div>Home</div>
<div>Über</div>
</nav>
footer.latte
<footer>
<div>Copyright</div>
</footer>
layout.latte
{include 'header.latte'}
<main>{block main}{/block}</main>
{include 'footer.latte'}
{layout}
Anwendungsfall: Erweitern von layout.latte
innerhalb von homepage.latte
und
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>Über-Seite</p>
{/block}
{import}
Anwendungsfall: sidebar.latte
in single.product.latte
und
single.service.latte
.
sidebar.latte
{block sidebar}<aside>Dies ist die Seitenleiste</aside>{/block}
single.product.latte
{layout 'product.layout.latte'}
{import 'sidebar.latte'}
{block main}<main>Produktseite</main>{/block}
single.service.latte
{layout 'service.layout.latte'}
{import 'sidebar.latte'}
{block main}<main>Dienstleistungsseite</main>{/block}
{define}
Anwendungsfall: Funktionen, denen Variablen übergeben werden und die etwas rendern.
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}
Anwendungsfall: Einbetten von pagination.latte
in product.table.latte
und
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}Erste Produktseite{/block}
{block last}Letzte Produktseite{/block}
{/embed}
service.table.latte
{embed 'pagination.latte', min: 1, max: $services->count}
{block first}Erste Dienstleistungsseite{/block}
{block last}Letzte Dienstleistungsseite{/block}
{/embed}
`
latte