Was ist eine Template-Engine?
Jedes moderne Web-Framework besitzt eine Template Engine.
Eine Template Engine ist ebenfalls ein Framework, dass es erleichtert den HTML-Teil, also das Template, umzusetzen.
Ein sehr wichtiger Bestandteil von modernen Template Engine's ist es, dass Design von Programmlogik getrennt sein muss. In unserem Fall HTML von PHP.
Das ist notwendig, damit man Software besser warten, aktualisieren und skalieren kann. In Anwendungen, wo das Template mit der Programmlogik gemischt wird, ist das sehr viel schwieriger und aufwändiger.
Einführung in TYPO3 Fluid
(c) https://typo3.org/fluid
Fluid ist die Template-Engine vom TYPO3 CMS und basiert auf XML. Im Gegensatz zu vielen anderen Template Engines wo nur versprochen wird, dass die Programmlogik getrennt ist, ist das bei Fluid Realität.
Ich habe schon mit vielen Content-Management-Systemen, wie Joomla oder WordPress gearbeitet. Habe auch PHP Standalone Anwendungen mit der Template-Engine Smarty vor Jahren betreut.
In jeder dieser Fälle findet man PHP Code in den Templatedateien.
TYPO3 Fluid dagegen besteht vollständig und lückenlos aus XML. Dennoch lassen sich dynamische und komplexere Templates schreiben. Man muss nicht auf IF oder SWITCH-Bedingungen oder auf FOR-Schleifen verzichten. Das alles gibt es in XML Schreibweise.
Ein Fluid-Beispiel:
<f:asset.script identifier="t3t_bootstrap_tabs_js" src="EXT:t3templates_base/Resources/Public/JavaScript/jquery.t3t_bootstrap_tabs.js" />
<f:if condition="{t3tTemplates.dynamicCss}">
<f:asset.css identifier="t3templates_dynamiccss">
<f:format.raw>{t3tTemplates.dynamicCss}</f:format.raw>
</f:asset.css>
</f:if>
<div class="tabs {t3tContainer.tabsContainer.tabs.css}">
<f:render section="NavTabs" arguments="{tabs: items}" />
<div class="tab-content">
<f:for each="{items}" as="tab" iteration="it">
<f:format.raw>{tab.renderedContent}</f:format.raw>
</f:for>
</div>
</div>
Du siehst im obigen Beispiel, wie man JavaScript und CSS Dateien gezielt einbinden kann. Du findest Bedingungen und Schleifen. Das alles ohne eine einzige Zeile PHP Code.
Diese Tags, die dafür nötig sind, sind keine HTML-Standardtags, sondern kommen von Fluid. Diese Fluid-Tags werden auch ViewHelper genannt.
Ein weiteres wichtiges Merkmal von Fluid ist es, dass es eine sehr sichere Template-Engine ist.
Alles, was man in Fluid ausgibt, wird standardmäßig escaped und sanitized. Das bedeutet, wenn man HTML oder JavaScript ausgibt, dann wird dieses HTML lesbar angezeigt und nicht als HTML ausgeführt. Wenn man sowas möchte, dann muss man das über bestimmte ViewHelper tun.
Fluid ist sehr stark erweiterbar, da man sehr einfach eigene ViewHelper, also XML Tags, erstellen kann. Damit lässt sich dann jede Funktionalität in Fluid umsetzen, ohne PHP einzusetzen innerhalb der Templatedateien.
Welche Fluid ViewHelper gibt es?
Es gibt eine große Anzahl an ViewHelper in TYPO3. Ein Teil kommen direkt aus dem TYPO3 Core und andere aus dem ausgelagerten TYPO3 Fluid Projekt.
Ein großer Teil der Fluid ViewHelper Entwicklung wurde aus der TYPO3 CMS Entwicklung ausgekoppelt, sodass die Weiterentwicklung von Fluid unabhängig vom TYPO3 CMS geschehen kann.
Eine vollständige Liste an Fluid ViewHelper findest du in der Fluid ViewHelper Dokumentation innerhalb der TYPO3 Dokumentation.
Ich möchte dir hier nur einen kleinen Auszug an ViewHelper geben.
Link ViewHelper
Ein wichtiger Punkt bei der Entwicklung innerhalb eines Web-Frameworks, ist es Links oder URLs zu generieren. Das ist speziell und besonders wichtig, da jedes Web-Framework sein eigenes Routing hat.
Jeder Link, der nun erzeugt wird und innerhalb einer Anwendung zeigt, der muss dieses Routing berücksichtigen.
Innerhalb TYPO3 in Fluid gibt es dafür den Link ViewHelper bzw. den Uri ViewHelper.
Hier ein praktisches Beispiel:
<f:link.typolink parameter="{data.header_link}" class="btn btn-primary"><i class="fa-solid fa-arrow-right"></i></f:link.typolink>
...style="background-image: url({f:uri.image(src:'{settings.background.image}',treatIdAsReference:'1')});"...
Form ViewHelper
Formulare sind ein weiteres großes Thema in einer Webanwendung. In Fluid gibt es auch dafür ein eigenen ViewHelper zu dem noch viele weitere ViewHelper gehören. Diese weiteren ViewHelper erstellen die Formularfelder und Buttons.
Formulare in Fluid nehmen eine ganz besondere Rolle ein, da hier durch Angabe der Attribute: name und object. Ein PHP Objekt angegeben werden kann. Dieses PHP Objekt wird dann automatisch mit Werten befüllt und die Formularfelder automatisch mit Werten vorbelegt, falls man ein existierendes PHP Objekt dem Formular übergibt.
Diese Mechanik ist extrem praktisch, weil man nicht länger manuell die Formulardaten irgendwo hin speichern muss. Ebenso kann man sich die Benennung der Formularfelder sparen.
Das alles geschieht vollautomatisch.
Es lässt sich sogar Validatoren an den Objekteigenschaften knüpfen, wenn man dafür eine eigene Klasse definiert hat. Alternativ oder ergänzend dazu, kann man einen Validator am ganzen Objekt bei der Übergabe definieren.
Das Ganze ist ein komplexeres Thema und sprengt diesen Blogartikel, weshalb ich das hier nur anreißen möchte.
Die vollständige Form ViewHelper Dokumentation gibt es hier.
Ein Beispiel sieht wie folgt aus:
<f:form action="update" method="POST" name="themeSettings" object="{themeSettings}">
<div class="mb-3">
<label for="primaryColor" class="form-label"><f:translate key="themeditor.form.primaryColor" extensionName="t3templates_base" /></label>
<f:form.textfield type="text" class="t3tthemeeditor-colorpicker form-control" property="primaryColor" data="{t3tmpls-cssVar: '--bs-primary', t3tmpls-type: 'color'}" />
</div>
<div class="mb-3">
<label for="secondaryColor" class="form-label"><f:translate key="themeditor.form.secondaryColor" extensionName="t3templates_base" /></label>
<f:form.textfield type="text" class="t3tthemeeditor-colorpicker form-control" property="secondaryColor" data="{t3tmpls-cssVar: '--bs-secondary', t3tmpls-type: 'color'}" />
</div>
<div class="mb-3">
<f:form.submit class="form-control btn btn-primary" name="submit" value="{f:translate(key:'themeditor.form.submit', extensionName: 't3templates_base')}" />
</div>
</f:form>
Wenn du tiefer in diese Materie einsteigen möchtest, dann buche dort ein TYPO3 Coaching.
Image ViewHelper
Neben Text möchte man natürlich auch Bilder in seine Templates einbinden. TYPO3 bietet hier automatisch erstellbare Thumbnails und Responsive Images an. Dazu muss man lediglich den Image ViewHelper nutzen.
Ein einfaches Beispiel sieht wie folgt aus:
<f:image image="{files.0}" class="card-img-top" />
Du kannst Bilder einbinden über eine URL oder als TYPO3 FileReference.
Der Image ViewHelper bietet alle Attribute, die das normale IMG-Tag hat und ergänzt diese um diverse TYPO3 spezifische. Das gilt im Übrigen für alle ViewHelper, die ein Standard HTML-Tag ausgeben.
In der Dokumentation vom Image ViewHelper findest du komplette Liste an Attribute und Einbindungsmöglichkeiten.
IF ViewHelper
Den IF-ViewHelper hast du bereits oben in der Praxis gesehen. Intern wird die PHP IF - Funktion ausgeführt und dementsprechend ist auch die Handhabung ähnlich.
Man definiert einen Bedingung (Condition) und bestimmt dann, was passieren soll, wenn die Bedingung zutrifft und wenn sie nicht zutrifft.
Dieser ELSE - Teil ist optional. Dementsprechend kann eine If-Bedingung in Fluid so aussehen:
<f:if condition="{files -> f:count()}"> <f:image image="{files.0}" class="card-img-top" /> </f:if>
oder mit ELSE-Teil so:
<f:if condition="{layout} == 0">
<f:then>
<f:variable name="cardHeaderTag" value="h2" />
</f:then>
<f:else>
...
</f:else>
</f:if>
FOR ViewHelper
Als letzten ViewHelper möchte ich den FOR-ViewHelper vorstellen. Ein praktisches Beispiel findest du weiter oben im Artikel.
Der FOR-ViewHelper ist intern eine foreach - Schleife. Damit lassen PHP Arrays bzw. Iterables in Fluid durchlaufen und somit Tabellen, Listen oder listenähnliche Konstrukte in Fluid erzeugen.
Ein klassisches Beispiel wäre ein Menü:
<f:for each="{nav}" as="navItem">
<li class="nav-item">
<a href="{navItem.link}{f:if(condition: navItem.data.t3t_url_hash, then:'#c{navItem.data.t3t_url_hash}')}" class="nav-link{f:if(condition: '{navItem.active} && {navItem.data.t3t_url_hash} == ""', then: ' active')}{f:if(condition: '{navItem.current} && {navItem.data.t3t_url_hash} == ""', then: ' current')}" target="{navItem.target}">{navItem.title}</a>
<f:if condition="{navItem.children}">
<f:render partial="Nav/NavChildren" arguments="{nav: navItem.children}" />
</f:if>
</li>
</f:for>
Template-Dateien strukturieren
Die Erstellung von diversen HTML-Tags ist die eine Aufgabe einer Template-Engine. Die andere Aufgabe ist es, sinnvolle Wege zu finden, Codeabschitte wiederverwendbar zu machen.
Um sein Template aufzuteilen und Codeabschnitte wiederzuverwenden, gibt es in TYPO3 Fluid folgende Unterteilungen:
- Template
- Layout
- Partials
- Section
Template
Alles beginnt mit einer Templatedatei und diese Datei ist, die einzige, die Pflicht ist, um in TYPO3 den View Teil des MVC Modells zu implementieren.
Für sehr einfache Templates, die keine wiederverwendbaren Teile besitzen, ist das auch ausreichend.
In den allermeisten Fällen jedoch sind die Templates komplexer oder haben bereits sehr früh Abschnitt, die man gerne wiederverwenden möchte.
Layout
Das Layout ist ebenfalls eine separate Datei innerhalb von TYPO3 Fluid.
In dieser Datei definiert man das Außengerüst, in dem der eigentliche Inhalt sich befindet.
Für ein Seitentemplate könnte die Layoutdatei wie folgt aussehen:
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<header id="pageHeader" class="sticky-top">
<div class="container d-flex flex-wrap justify-content-center">
<f:render partial="Header/Brand" arguments="{_all}" />
<f:render partial="Nav/MainNav" arguments="{_all}" />
</div>
</header>
<!--TYPO3SEARCH_begin-->
<div class="container"><f:render section="Main" /></div>
<!--TYPO3SEARCH_end-->
<footer id="pageFooter" class="bg-{f:if(condition:'{settings.footer.colorScheme}', then:'{settings.footer.colorScheme}', else: 'default')}">
<f:cObject typoscriptObjectPath="lib.t3tmpls.footer" data="{pageId: settings.footer.pid}" />
</footer>
</html>
Dieses HTML Gerüst wird in jedem Template genutzt, welches dieses Layout benutzt. Der Inhalt der Templatedatei wird dann mittels Section eingefügt (mehr zu Section siehe unten).
In diesem Beispiel geschieht das in Zeile 9 mit dem ViewHelper:
<f:render section="Main" />
Der Vorteil ist, dass man in der Templatedatei diese HTML Teile nicht noch einmal reinkopieren muss.
Das ist besonders praktisch, wenn man mehrere Templates hat, die nur im Inhaltsbereich variieren und immer das gleiche Gerüst haben sollen.
Partials
Ein Partial ist ebenfalls eine eigene Datei in Fluid.
Diese Datei enthält HTML Teile, die wiederverwendet werden können. Dazu muss mittels
<f:render partial="Partial" arguments="{..}" />
die Partialdatei einbinden.
Über das Attribut: arguments können dynamische Werte übermittelt werden.
Ein Partial kann in einer Templatedatei eingebunden werden und in einem Partial selber auch.
Ein Partial kann kein eigenes Layout haben.
Hier ein Beispiel für den Einsatz einer Partialdatei.
Eine Templatedatei für Textpic bindet das Bild über ein Partial ein. Das ist sinnvoll, weil auch andere Inhaltselemente von TYPO3 Bilder haben können.
<div class="ce-textpic ce-{gallery.position.horizontal} ce-{gallery.position.vertical}{f:if(condition: gallery.position.noWrap, then: ' ce-nowrap')}">
<f:if condition="{gallery.position.vertical} != 'below'">
<f:render partial="Media/Gallery" arguments="{_all}" />
</f:if>
...
</div>
Innerhalb der Partialdatei können dann noch mehrere Abfragen und Bedingungen abgefragt werden:
<div class="ce-gallery{f:if(condition: data.imageborder, then: ' ce-border')}" data-ce-columns="{gallery.count.columns}" data-ce-images="{gallery.count.files}">
<f:for each="{gallery.rows}" as="row">
<div class="ce-row">
<f:for each="{row.columns}" as="column">
<f:if condition="{column.media}">
<div class="ce-column">
<f:render partial="Media/Type" arguments="{file: column.media, dimensions: column.dimensions, data: data, settings: settings}" />
</div>
</f:if>
</f:for>
</div>
</f:for>
...
Du siehst auch, dass dieser Partial weitere Partialdateien einbinden kann.
Falls du dich wunderst, was _all bei arguments zu bedeutet hat, dann hier die Erläuterung dazu.
_all ist ein Steuerzeichen, was alle verfügbaren Fluidvariablen weiterleitet. Das erspart viel Tipparbeit, wenn man alle weiterleiten möchte.
Ansonsten muss man jede Fluidvariable einzeln übergeben, falls man nicht alle weiterleiten möchte bzw. nicht alle benötigt.
Section
Eine Section hat den gleichen Nutzen wie ein Partial. Sie soll für ein Template HTML Teile zentral speichern und wiederverwendbar machen.
Die Funktionsweise ist nahezu identisch und du hast auch schon hier mehrfach die Nutzung von einer Section gesehen.
Es gibt jedoch einen fundamentalen Unterschied zu einem Partial.
Eine Section wird nicht in einer separaten Datei gespeichert, sondern ist immer Teil einer Template- oder Partialdatei.
Deswegen nutzt man Section für die Codeteile, die nur innerhalb eines Templates oder Partials ausgelagert werden sollen.
Durch den geschickten Einsatz von Section lassen sich auch rekursive Algorithmen in Fluid schreiben.
Fazit
Du siehst Fluid ist eine sehr klare Template-Engine, die alle HTML Tags unterstützt und zu den wichtigsten Aufgaben ViewHelper bereitstellt.
So lassen sich sehr einfach Links und Bilder einbinden und Bedingungen und Schleifen ausführen.
Durch das automatische Escaping ist Fluid sicher und schützt zuverlässig vor Cross Site Scripting (XSS).
Mit gefällt besonders die einfacher Erweiterbarkeit und das man komplett auf PHP Code in den Templatedateien verzichten kann.
Falls du Hilfe bei der Einarbeitung in Fluid brauchst oder ein konkretes Problem mit mir besprechen möchtest. Dann buche ein TYPO3 Coaching bei mir.
Falls dich die Enwicklung von TYPO3 interessiert, dann empfehle ich dir meinen T3 Campus.