Mehr als nur Old-School-Templating: TypoScript Reloaded
Zeitreise rückwärts in die Vergangenheit: Heute kann man mit Templavoila (TV) recht einfach das Layout in die Website integrieren. Vorher bestand das Templating in der Nutzung eines reinen TypoScripts. TV schickte sich an, den „alten“ Modern-Template-Building-Ansatz abzulösen.
Aber keine Angst, um unserem Kurs zu folgen, müssen Sie keine antiquarische TYPO3-Version 3.3 installieren. Ganz im Gegenteil, wir werden ausgiebig Gebrauch machen von Erweiterungen und Weiterentwicklungen, die in den Versionen V3.8 bis 4.x Einzug gehalten haben. Syntax und Struktur [1] von TypoScript jedoch setze ich – ebenso wie Grundlagen zu SQL – als bekannt voraus.
TypoScript steht zu Unrecht in dem Ruf, kompliziert zu sein. Wahrscheinlich ist es diesem Vorurteil geschuldet, dass oft unnötigerweise Extensions oder userFuncs für einfachste Funktionen geschrieben werden. Für viele Anwendungen kommt man jedoch mit reinem TypoScript schneller und flexibler zum Ziel.
Als Etappenziel werden wir eine Liste von Frontend-Usern dynamisch erzeugen und, ausgehend von einer einfachen Liste, „Features“ hinzufügen, bis wir schließlich eine Liste erhalten, welche die fünf zuletzt eingeloggten User anzeigt, sortiert nach Zeit und Datum ihres letzten Logins und jeweils verlinkt zu ihrer persönlichen Profilseite. Doch vorab sollten wir uns ins Gedächtnis rufen, wie TYPO3 beim Rendering vorgeht. Unverzichtbares Werkzeug dafür ist der TypoScript-Object-Browser, kurz TSOB (siehe auch „Stressfreie Fehlersuche“).
page.10 ist also ein solches. cObjects sind neben den stdWraps eine der grundlegendsten Funktionen.
Das Objekt darunter ist vom Typ CONTENT, es tritt meist nur sehr unscheinbar in Gestalt von styles.content.get auf, was man mit dem ObjectBrowser (oder durch Studium des CSS Styled Content Templates) leicht herausfinden kann. Aber genau dieses wird uns im weiteren Verlauf genauer interessieren.
So wie es hier verwendet wurde, befragt es die Datenbanktabelle tt_content und holt damit die Inhalte, also Content Elemente (CE) einer Backend-Spalte, auf die Seite. Es spricht jedoch nichts dagegen, den Inhalt aus einer ganz anderen Tabelle zu beziehen. Beispiel:
lib.userlist = CONTENT lib.userlist { table = fe_users // id des Sysordners mit Frontend Usern select.pidInList = 51 renderObj = TEXT renderObj { // Username field = username wrap = |<br /> } }
Listing 1
Eingebunden wird das mit den folgenden Codezeilen:
page = PAGE page { 10 < lib.userlist }
Listing 2
Mit nur sechs TypoScript-Anweisungen erhalten wir schon eine unsortierte Liste aller Frontend-User im angegebenen Sysordner. Bevor die Funktionsweise erklärt wird, bohren wir dieses Beispiel noch ein wenig auf:
lib.userlist = CONTENT lib.userlist { table = fe_users select.pidInList = 51 select.andWhere = lastlogin > ({date:U}-864000) select.andWhere.insertData = 1 select.orderBy = lastlogin DESC // nur max 5 User select.max = 5 renderObj = COA renderObj { 10 = IMAGE 10.file = fileadmin/img/email.gif 10.altText = write an email 10.stdWrap.typolink.parameter.field = email 20 = TEXT 20.field = username 30 = TEXT // Zeitstempel letzter Login 30.field = lastlogin // formatiere als Zeitdifferenz zum aktuellen Zeitstempel 30.age = Min|Std|Tage 30.noTrimWrap = | (|) | wrap = <div class=“user-entry“>|</div> } wrap = <div class=“user-list“><h3>Logins:</h3>|</div> }
Listing 3
Wie funktioniert das nun? Zuerst wird mit „table“ und „select“ der Datenbank-Query konfiguriert. Wenn man bei der Formulierung von Datenbankabfragen nicht so geübt ist, bietet sich die Suche von phpMyAdmin an, die den ausgeführten Query am Bildschirm ausgibt. Über die select-Attribute lassen sich, neben den hier verwendeten, auch (fast) alle anderen Aspekte eines SQL-Query wie GROUP BY oder Joins definieren. Details und weitere Optionen dazu hält die TSref bereit.
Stressfreie Fehlersuche |
|
Beschränkungen von CONTENT bzw. select |
|
Für jeden Datensatz (hier User) wird dann das renderObj durchlaufen. Es ist selbst vom Typ cObject, kann also jedes Content-Objekt aus der TSref [siehe Kapitel 8] aufnehmen. Das sieht man sehr gut, wenn man Beispiel 1 und 2 vergleicht. Im ersten haben wir direkt ein TEXT verwendet, im zweiten ein COA, womit auch der Aufbau umfangreicherer Konstrukte übersichtlich bleibt. Aus der Praxis lässt sich sagen, dass mit TEXT, COA, IMAGE und CASE bereits 90 Prozent der Anwendungsfälle abdeckbar sind. Aber auch eine Unterabfrage (z. B. um die FE-User-Gruppen jedes Users mitanzuzeigen) lässt sich mit einem weiteren CONTENT im renderObj des fe_users-CONTENT realisieren. Wir werden darauf im nächsten Teil näher eingehen.
Für jeden Durchlauf stellt das CONTENT-Objekt uns einen Datensatz im aktuellen stdWrap-Kontext bereit. Bezogen auf die CE 10, 20 und 30 lässt sich also feststellen, dass dort jeweils alle Felder (Datenbankspalten) eines Abfrageergebnisses zur Verfügung stehen. Wir nutzen das über die stdWrap-Eigenschaften des TEXT-Objekts. Es hält über die Attribute „field“, „data“, „cObject“ und „value/insertData“ gleich mehrere Möglichkeiten bereit.
An dieser Stelle sei noch einmal der Ratschlag gegeben, Links ausschließlich (!) mit Typolink zu erzeugen, statt beispielsweise mit wrap einen mailto-Link per Hand zu erzeugen. Richtig eingesetzt sind Typolinks kurz, klar und funktionieren in jeder TYPO3-Umgebung, d.h. mit/ohne Simulate Static oder auch als Real Url. In unserem Beispiel würde zum Beispiel auch die E-Mail-Adresse automatisch verschlüsselt werden [2]
[3], wenn dies per config.spamProtectEmailAddresses eingestellt ist.
Nun enthält dieses Beispiel noch ein Problem. Wir setzen voraus, dass zu jedem User eine E-Mail-Adresse hinterlegt ist. Wäre in der Datenbank dieses Feld bei einigen Usern leer, so gibt es eine gute und eine schlechte Nachricht. Die gute zuerst: Da wir die TypoLink-Funktion genutzt haben und keinen wrap, wird immerhin der komplette Link weggelassen. Die schlechte: Die Grafik, um die der Link gelegt wird, ist weiterhin sichtbar. Als Lösung erweitern wir das Beispiel erneut:
renderObj { : 10.if.isTrue.field = email : }
Listing 4
Nun erhalten wir eine Ausgabe, die die Grafik immer dann ausblendet, wenn keine E-Mail hinterlegt ist. Wollen wir diese Grafik stattdessen durch eine andere ersetzen, so fügen wir oben zusätzlich ein:
renderObj { : 10.stdWrap.override.cObject = IMAGE 10.stdWrap.override.cObject.file = fileadmin/img/no_email.gif 10.stdWrap.override.if.isFalse.field = email : }
Listing 5
Die finale Ausgabe sollte dann aussehen, wie die nebenstehende Abbildung. Durch den Einsatz von „if“, „ifEmpty“ und „override“ lässt sich eine Flexibilität erreichen, die bei klassischen Markern in den Templates von Extensions allzu oft schmerzlich vermisst wird.
Früher oder später wird der Punkt kommen, an dem man sehr viele Felder in einem Schwung abrufen möchte. Bisher haben wir die Datenbankfelder mit der Funktion „field“ abgeholt. Doch auch per getText [siehe TSref Kapitel 2.2] stehen sie uns bereit.
Stellen wir uns vor, wir wollen den Usernamen verlinken, wobei der Link als Parameter die ID der aktuellen Seite und die ID des Users enthält, um damit eine Seite anzusteuern. Die Seiten-ID benötigt man des Öfteren, um auf einer separaten Seite weitere Details darzustellen und von dort schließlich mit einem Link wieder zurückverlinken zu können.
20.typolink.parameter = 52 20.typolink.additionalParams = &fe_user={field:uid}&backPID={TSFE:id} 20.typolink.additionalParams.insertData = 1
Listing 6
Wir bedienen uns bei der Bildung der URL einer starken Funktion von stdWrap und verwenden Platzhalter nach dem Muster {<Anweisung>:<Parameter>}. Damit sie nicht als Text, sondern als Anweisung, konkret als getText, interpretiert werden, muss dies noch mit insertData = 1 bekanntgemacht werden.
Nach diesem Muster lassen sich Links zur Ansteuerung von Seiten erzeugen, auf denen Plugins liegen. Die Plugins können diese(n) Parameter dann empfangen und interpretieren.
Auch bei der SQL-WHERE-Klausel in Listing 3 haben wir schon von getText Gebrauch gemacht, denn {date:<format>} liefert das aktuelle Datum im angegebenen Format, wobei U dort für das Format Unix Timestamp [4] steht. Im Klartext bedeutet der Ausdruck also: Liefere alle Datensätze, bei denen der letzte Login größer (also neuer, aktueller) vom aktuellen Zeitpunkt 864.000 Sekunden (= 10 Tage) rückwärts gerechnet ist.
Der kleine Unterschied
stdWrap Eigenschaft | Wirkung |
field = uid
wrap = userid=| |
Übernimm den Inhalt aus der Spalte uid der letzten Datenbankabfrage und umschließe ihn mit userid=|. |
data = field:uid
wrap = userid=| |
Werte den getText in data aus und umschließe ihn. |
current = userid={field:uid}
current.insertData = 1 |
Lade den Content mit der Zeichenkette aus current und ersetze alle Vorkommen von {…} als getText. |
Die Ausdrücke in dieser Tabelle bewirken scheinbar alle das Gleiche. In dieser einfachen Form ist das tatsächlich so.
Interessant wird es nun, wenn man sich die Unterschiede vor Augen führt. Version 1 und 2 unterscheiden sich nur unwesentlich. Die zweite benutzte getText und ist damit noch einen Tick universeller, kann man damit doch auch in jedem Kontext die aktuelle Seiten-ID, die Informationen aus der Rootline und Ähnliches erfragen. Das dritte Beispiel ist viel kompakter und flexibler. Man kann mehrere getText-Anweisungen in eine Zeile schreiben. Wir werden im nächsten Teil tiefer darauf eingehen.
Fazit und Ausblick
Wir haben mit wenigen Zeilen TypoScript eine Userliste entwickelt, die in PHP verfasst mindestens den doppelten bis dreifachen Umfang eingenommen hätte. Gleichwohl gibt es natürlich Situationen, in denen sich dieses Verhältnis schnell umkehren kann. Die Auswirkungen einer getroffenen Entscheidung vorauszusehen und zu planen kann manchmal nicht ganz einfach sein. Einige der Beschränkungen des CONTENT-Objekts sind offensichtlich. Systemtabellen oder nicht-TYPO3-Tabellen zu befragen, ist unmöglich [5], lange Listen möchte man gern durchblättern können und die propagierte Trennung von Code, Inhalt und Design ist uns auch etwas verloren gegangen.
Die meisten der oben genannten Beschränkungen von CONTENT werden wir im nächsten Teil mit einer von mir entwickelten Extension aufheben. Außerdem werden als Fingerübung weitere stdWrap-Eigenschaften wie Bedingungen und Umwandlungen eingesetzt, wenn wir die Profilseite für einen User dynamisch erzeugen.