Template-Vererbung und Wiederverwendbarkeit
Die Mechanismen für Wiederverwendbarkeit und Vererbung von Templates steigern Ihre Produktivität, denn jedes Template enthält nur seinen einzigartigen Inhalt und wiederkehrende Elemente und Strukturen werden wiederverwendet. Wir stellen drei Konzepte vor: Layout-Vererbung, Horizontale Wiederverwendung und Einheiten-Vererbung.
Das Konzept der Template-Vererbung in Latte ähnelt der Vererbung von Klassen in PHP. Sie definieren ein Eltern-Template, von dem andere Kind-Templates erben und Teile des Eltern-Templates überschreiben können. Das funktioniert hervorragend, wenn Elemente eine gemeinsame Struktur haben. Klingt kompliziert? Keine Sorge, es ist ganz einfach.
Layout-Vererbung {layout}
Schauen wir uns die Vererbung von Layout-Templates an einem Beispiel an. Dies ist ein Eltern-Template, nennen wir es
layout.latte, das das Skelett eines HTML-Dokuments definiert:
<!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}© Copyright 2008{/block}
</div>
</body>
</html>
Die {block}-Tags definieren drei Blöcke, die Kind-Templates füllen können. Der Block-Tag tut nichts weiter, als
der Template-Engine mitzuteilen, dass ein Kind-Template diese Stellen überschreiben darf, indem es einen eigenen Block mit
demselben Namen definiert.
Ein Kind-Template könnte so aussehen:
{layout 'layout.latte'}
{block title}My amazing blog{/block}
{block content}
<p>Welcome to my awesome homepage.</p>
{/block}
Der Schlüssel ist hier der Tag {layout}. Er sagt Latte, dass dieses Template ein anderes Template
“erweitert”. Wenn Latte dieses Template rendert, sucht es zuerst das Eltern-Template – in diesem Fall
layout.latte.
In diesem Moment 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 Block footer nicht definiert hat, wird stattdessen der Inhalt aus dem
Eltern-Template verwendet. Der Inhalt innerhalb eines {block}-Tags im Eltern-Template dient immer als
Rückfallebene.
Die Ausgabe könnte so aussehen:
<!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">
© Copyright 2008
</div>
</body>
</html>
In einem Kind-Template stehen Blöcke üblicherweise auf der obersten Ebene oder innerhalb eines anderen Blocks, zum Beispiel:
{block content}
<h1>{block title}Welcome to my awesome homepage{/block}</h1>
{/block}
Außerdem wird ein Block immer erzeugt, 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 also durchaus.
{if false}
{block head}
<meta name="robots" content="noindex, follow">
{/block}
{/if}
Wenn die Ausgabe innerhalb des Blocks nur unter einer Bedingung erscheinen soll, verwenden Sie stattdessen Folgendes:
{block head}
{if $condition}
<meta name="robots" content="noindex, follow">
{/if}
{/block}
Code im Kopf des Kind-Templates (also vor dem ersten Block oder einer beliebigen Ausgabe) wird ausgeführt, bevor das
Layout-Template gerendert wird. Sie können ihn daher nutzen, um Variablen wie {var $foo = bar} zu definieren und
Daten durch die gesamte Vererbungskette zu reichen. Code, der in einem Template mit {layout} zwischen oder nach den
Blöcken steht, wird überhaupt nicht ausgeführt:
{layout 'layout.latte'}
{var $robots = noindex}
...
Wenn Sie Variablen nur an das Layout übergeben möchten, ohne sie im aktuellen Template zu erzeugen,
geben Sie sie im Tag {layout} (oder {extends}) direkt nach einem Komma an:
{layout 'layout.latte', robots: noindex}
Die Variable $robots steht im Layout und in dessen Blöcken zur Verfügung, nicht aber in den Blöcken des
aktuellen Templates. Eine explizit übergebene Variable hat außerdem Vorrang vor einem gleichnamigen Template-Parameter.
Mehrstufige Vererbung
Sie können beliebig viele Vererbungsebenen verwenden. Eine verbreitete Art, die Layout-Vererbung zu nutzen, ist der folgende dreistufige Ansatz:
- Erstellen Sie ein Template
layout.latte, das das grundlegende Aussehen Ihrer Website enthält. - Erstellen Sie für jeden Bereich Ihrer Website ein Template
layout-SECTIONNAME.latte, zum Beispiellayout-news.latte,layout-blog.latteusw. Alle diese Templates erweiternlayout.latteund enthalten Styles und Design, die für den jeweiligen Bereich spezifisch sind. - Erstellen Sie einzelne Templates für jeden Seitentyp, etwa für einen Nachrichtenartikel oder einen Blogbeitrag. Diese Templates erweitern das passende Bereichs-Template.
Dynamische Layout-Vererbung
Als Namen des Eltern-Templates können Sie eine Variable oder einen beliebigen PHP-Ausdruck verwenden, sodass sich die Vererbung dynamisch verhalten kann:
{layout $standalone ? 'minimum.latte' : 'layout.latte'}
Sie können das Layout-Template auch über die Latte-API automatisch auswählen lassen.
Tipps
Hier ein paar Tipps für die Arbeit mit der Layout-Vererbung:
- Wenn Sie
{layout}in einem Template verwenden, muss es im Kopf des Templates stehen, also vor jeder Ausgabe. Davor dürfen nur Tags stehen, die keine Ausgabe erzeugen (wie{var},{templateType},{import}oder Kommentare). - Das Layout kann automatisch gefunden
werden (wie bei Presentern). Soll das
Template in diesem Fall kein Layout haben, teilt es das mit dem Tag
{layout none}mit. Umgekehrt stellt{layout auto}(oder{extends auto}) die automatische Suche wieder her. - Der 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 Blöcke des Eltern-Templates definieren müssen. Sie können also in mehreren Blöcken sinnvolle Standardwerte hinterlegen und später nur die definieren, die Sie wirklich brauchen.
Blöcke {block}
Siehe auch den anonymen {block}
Ein Block bietet die Möglichkeit zu ändern, wie ein bestimmter Teil eines Templates gerendert wird, greift aber in keiner Weise in die Logik drumherum ein. Zeigen wir am folgenden Beispiel, wie ein Block funktioniert und, noch wichtiger, 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 die {block}-Tags exakt dasselbe. Blöcke haben
Zugriff auf Variablen aus äußeren Gültigkeitsbereichen. Sie bieten 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}
Wird nun das Kind-Template gerendert, verwendet die Schleife den im Kind-Template child.latte definierten Block
statt des in parent.latte definierten; 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 innerhalb eines benannten Blocks eine neue Variable anlegen oder den Wert einer bestehenden 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 lässt sich mit Filtern verändern. Das folgende Beispiel entfernt sämtliches HTML und ändert die Groß-/Kleinschreibung:
<title>{block title|stripHtml|capitalize}...{/block}</title>
Der Tag kann auch als n:Attribut geschrieben werden:
<article n:block=post>
...
</article>
Lokale Blöcke
Jeder Block überschreibt den Inhalt des gleichnamigen Blocks im Eltern-Template – mit Ausnahme der lokalen Blöcke. Sie sind das Gegenstück zu privaten Methoden in Klassen. Sie können ein Template erstellen, ohne befürchten zu müssen, dass die Blöcke durch eine zufällige Namensgleichheit von 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 den Tag {include blockname}:
<title>{block title}{/block}</title>
<h1>{include title}</h1>
Sie können auch einen Block aus einem anderen Template ausgeben:
{include footer from 'main.latte'}
Der gerenderte Block hat keinen Zugriff auf die Variablen des aktiven Kontexts, es sei denn, er ist in derselben Datei definiert, in der er eingebunden wird. Zugriff auf globale Variablen hat er jedoch immer.
Variablen übergeben Sie dem Block so:
{include footer, foo: bar, id: 123}
Der Blockname kann eine Variable oder ein beliebiger PHP-Ausdruck sein. In diesem Fall setzen Sie vor die Variable das
Schlüsselwort block, damit Latte schon beim Kompilieren weiß, dass es sich um einen Block handelt und nicht um ein
eingebundenes Template, 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 zum Beispiel 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}
Statt {include menu, ...} können wir auch {include this, ...} schreiben, wobei this den
aktuellen Block bezeichnet.
Der gerenderte Inhalt eines Blocks lässt sich mit Filtern verändern. Das folgende Beispiel entfernt sämtliches HTML und ändert die Groß-/Kleinschreibung:
<title>{include heading|stripHtml|capitalize}</title>
Eltern-Block
Wenn Sie den Inhalt des Blocks aus dem Eltern-Template ausgeben müssen, verwenden Sie {include parent}. Das ist
nützlich, wenn Sie den Inhalt des Eltern-Blocks ergänzen möchten, statt 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}
Definitionen {define}
Neben Blöcken kennt Latte auch “Definitionen”. In gängigen Programmiersprachen wären sie am ehesten mit Funktionen vergleichbar. Sie sind nützlich, um Template-Fragmente wiederzuverwenden und Wiederholungen zu vermeiden.
Latte hält die Dinge gern einfach, deshalb sind Definitionen im Grunde dasselbe wie Blöcke, und alles, was über Blöcke gesagt wurde, gilt auch für Definitionen. Sie unterscheiden sich von Blöcken darin, dass:
- sie in
{define}-Tags eingeschlossen sind - sie erst gerendert werden, wenn sie über
{include}eingefügt werden - man ihnen Parameter definieren kann, ähnlich wie Funktionen in PHP
{block foo}<p>Hello</p>{/block}
{* gibt aus: <p>Hello</p> *}
{define bar}<p>World</p>{/define}
{* gibt nichts aus *}
{include bar}
{* gibt aus: <p>World</p> *}
Stellen Sie sich vor, Sie haben ein Hilfs-Template mit einer Sammlung von Definitionen dafür, wie HTML-Formulare gezeichnet werden.
{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). Auch die Typen der Parameter lassen sich deklarieren:
{define input, string $name, ...}.
Das Template mit den Definitionen wird mit {import} geladen. Die
Definitionen selbst werden genauso wie Blöcke gerendert:
<p>{include input, 'password', null, 'password'}</p>
<p>{include textarea, 'comment'}</p>
Wie Blöcke haben auch Definitionen keinen Zugriff auf die Variablen des aktiven Kontexts, sondern nur auf globale Variablen. Die Ausnahme ist eine Definition, die keine deklarierten Parameter hat, über einen statischen Namen referenziert wird und in derselben Datei eingebunden wird, in der sie definiert ist: eine solche Definition hat Zugriff auf die Kontextvariablen der Stelle, von der aus sie eingebunden wird.
Dynamische Blocknamen
Latte erlaubt beim Definieren von Blöcken große Flexibilität, denn der Blockname kann ein beliebiger PHP-Ausdruck sein.
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, I am {$name}.{/block}
{/foreach}
Im Kind-Template können wir dann zum Beispiel nur einen einzigen Block neu definieren:
{block hi-John}Hello. I am {$name}.{/block}
Die Ausgabe sieht dann so aus:
Hi, I am Peter.
Hello. I am John.
Hi, I am Mary.
Existenzprüfung von Blöcken {ifset}
Siehe auch {ifset $var}
Mit dem Test {ifset blockname} prüfen Sie, ob ein Block (oder mehrere Blöcke) im aktuellen Kontext
existiert:
{ifset footer}
...
{/ifset}
{ifset footer, header, main}
...
{/ifset}
Der Blockname kann eine Variable oder ein beliebiger PHP-Ausdruck sein. In diesem Fall setzen Sie vor die Variable das
Schlüsselwort block, um klarzustellen, dass es nicht um die Existenzprüfung von Variablen geht:
{ifset block $name}
...
{/ifset}
Die Existenz von Blöcken prüft auch die Funktion hasBlock():
{if hasBlock(header) || hasBlock(footer)}
...
{/if}
Tipps
Ein paar Tipps für die Arbeit mit Blöcken:
- Der letzte Block auf der obersten Ebene braucht kein schließendes Tag, er endet mit dem Ende des Dokuments. Das vereinfacht das Schreiben von Kind-Templates, die einen einzigen Hauptblock enthalten.
- Für bessere Lesbarkeit können Sie den Blocknamen optional auch im Tag
{/block}angeben, zum Beispiel{/block footer}. Der Name muss allerdings mit dem Blocknamen übereinstimmen. In größeren Templates hilft Ihnen diese Technik zu erkennen, welche Block-Tags gerade geschlossen werden. - Mehrere Block-Tags mit demselben Namen können Sie im selben Template nicht direkt definieren. Über Dynamische Blocknamen lässt sich das aber erreichen.
- Zum Definieren von Blöcken können Sie n:Attribute verwenden,
etwa
<h1 n:block=title>Welcome to my awesome homepage</h1> - Blöcke lassen sich auch ohne Namen verwenden, nur um Filter auf
die Ausgabe anzuwenden:
{block|strip} hello {/block}
Horizontale Wiederverwendung {import}
Die horizontale Wiederverwendung ist der dritte Mechanismus für Wiederverwendung und Vererbung in Latte. Sie erlaubt es,
Blöcke aus anderen Templates zu laden. Das ähnelt dem Vorgehen in PHP, eine Datei mit Hilfsfunktionen anzulegen und sie dann per
require einzubinden.
Die Layout-Vererbung von Templates gehört zwar zu den stärksten Funktionen von Latte, ist aber auf einfache Vererbung beschränkt: Ein Template kann nur ein einziges anderes Template erweitern. Die horizontale Wiederverwendung ist der Weg zu einer Mehrfachvererbung.
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 Definitionen in ein anderes Template:
{import 'blocks.latte'}
{* die Blöcke sidebar und menu lassen sich nun verwenden *}
Wenn Sie die Blöcke im Eltern-Template importieren (also {import} in layout.latte verwenden), stehen
sie auch in allen Kind-Templates zur Verfügung, was sehr praktisch ist.
Das Template, das importiert werden soll (z. B. blocks.latte), darf kein anderes Template erweitern, also kein {layout} verwenden. Es kann jedoch selbst andere Templates
importieren.
Der Tag {import} sollte der erste Template-Tag nach {layout} sein. Der Name des Templates kann ein
beliebiger PHP-Ausdruck sein:
{import $ajax ? 'ajax.latte' : 'not-ajax.latte'}
Sie können in einem Template beliebig viele {import}-Anweisungen verwenden. Definieren zwei importierte Templates
denselben Block, gewinnt das erste. Das Haupt-Template hat allerdings die höchste Priorität und kann jeden importierten Block
überschreiben.
Der Tag {import} kann dem importierten Template auch Argumente übergeben, zum Beispiel
{import 'blocks.latte', foo: 1}. Diese Argumente stehen dann in den importierten Blöcken und Definitionen als
Variablen zur Verfügung.
Den Inhalt überschriebener Blöcke können Sie bewahren, indem Sie den Block genauso einfügen wie einen Eltern-Block:
{layout 'layout.latte'}
{import 'blocks.latte'}
{block sidebar}
{include parent}
{/block}
{block title}...{/block}
{block content}...{/block}
In diesem Beispiel ruft {include parent} den Block sidebar aus dem Template
blocks.latte auf.
Einheiten-Vererbung {embed}
Die Einheiten-Vererbung überträgt die Idee der Layout-Vererbung auf die Ebene einzelner Inhaltsfragmente. Während die Layout-Vererbung mit “Dokumentskeletten” arbeitet, die von Kind-Templates zum Leben erweckt werden, erlaubt die Einheiten-Vererbung, Skelette für kleinere Inhaltseinheiten zu erstellen und sie überall dort wiederzuverwenden, wo Sie möchten.
Bei der Einheiten-Vererbung ist der Tag {embed} der Schlüssel. Er vereint das Verhalten von
{include} und {layout}. Er erlaubt es, den Inhalt eines anderen Templates oder Blocks einzubetten und
optional Variablen zu übergeben, genau wie {include}. Und er erlaubt es, jeden im eingebetteten Template definierten
Block zu überschreiben, so wie {layout}.
Nehmen wir als Beispiel ein Akkordeon-Element. Sehen Sie sich das Skelett des Elements an, gespeichert im Template
collapsible.latte:
<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 Kind-Templates füllen können. Ja, genau wie beim Eltern-Template
in der Layout-Vererbung. Außerdem sehen Sie die Variable $modifierClass.
Setzen wir unser Element in einem Template ein. Hier kommt {embed} ins Spiel. Es ist ein außerordentlich
mächtiger Tag, mit dem wir all das auf einmal tun können: den Inhalt des Element-Templates einbetten, ihm Variablen mitgeben und
ihm Blöcke mit eigenem HTML hinzufügen:
{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}
Die Ausgabe könnte so aussehen:
<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>
Blöcke innerhalb von embed-Tags bilden eine eigene Schicht, die von den Blöcken außerhalb des embed isoliert ist. Sie
dürfen deshalb denselben Namen tragen wie ein Block außerhalb, ohne mit ihm zu kollidieren, und werden von ihm nicht
beeinflusst. Mit dem Tag include können Sie innerhalb der {embed}-Tags die
hier erzeugten Blöcke einfügen, Blöcke aus dem eingebetteten Template (die nicht lokal
sind) und außerdem Blöcke aus dem Haupt-Template, die 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, der Block ist innerhalb des embed definiert *}
{include hello} {* funktioniert, der Block ist in diesem Template lokal *}
{include content} {* funktioniert, der Block ist im eingebetteten Template definiert *}
{include aBlockDefinedInImportedTemplate} {* funktioniert *}
{include outer} {* funktioniert nicht! - der Block liegt in der äußeren Schicht *}
{/block}
{/embed}
Eingebettete Templates haben keinen Zugriff auf die Variablen des aktiven Kontexts, wohl aber auf globale Variablen.
Mit {embed} können Sie nicht nur Templates einbetten, sondern auch andere Blöcke. Das vorige Beispiel ließe
sich also auch so schreiben:
{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}
Es gibt allerdings einen Unterschied zwischen beiden: Betten Sie einen Block statt einer Datei ein, bleiben die Blöcke aus der
äußeren Schicht innerhalb des embed zugänglich. Anders als bei einer eingebetteten Datei würde {include outer}
dort also funktionieren.
Wenn wir {embed} einen Ausdruck übergeben und nicht klar ist, ob es sich um einen Blocknamen oder einen
Dateinamen 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 der Wiederverwendung von Code. Fassen wir die wichtigsten Konzepte der Übersichtlichkeit halber zusammen:
{include template}
Anwendungsfall: header.latte und footer.latte innerhalb von layout.latte
verwenden.
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}
Anwendungsfall: layout.latte in homepage.latte und about.latte erweitern.
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}
Anwendungsfall: sidebar.latte in single.product.latte und single.service.latte
verwenden.
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}
Anwendungsfall: Funktionen, die Variablen entgegennehmen und 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: pagination.latte in product.table.latte und service.table.latte
einbetten.
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}