Templates, CSS und TypoScript - Typo3: Extension selbst erstellen

Templates, CSS und TypoScript - Typo3: Extension selbst erstellen
Von Lars Ebert am 30.04.12, 10:40
Kategorien: Content Management Systeme, CSS, PHP, Programmieren, Tutorials and Typo3

Im letzten Teil der Artikelserie »Typo3: Exension selbst erstellen« haben wir begonnen, ein kleines Kontaktformular für Typo3 zu programmieren. Die Extension funktioniert zwar, doch kann man noch vieles verbessern. Deshalb fügen wir in diesem Artikel nun HTML-Templates, CSS-Styles und TypoScript-Konfiguration ein.

Aus Grundlage dient uns hier die Kontaktformular-Extension, die wir im letzten Artikel erstellt haben.

Artikelserie: Typo3: Extension selbst erstellen

Dieser Artikel ist Teil einer mehrteiligen Artikelserie. Lies dir auch die restlichen Teile durch!

  1. Kickstarter, Grundlagen & Hallo Welt - Typo3: Extension selbst erstellen
  2. Formulare, Parameter und Eingaben - Typo3: Extension selbst erstellen
  3. Templates, CSS und TypoScript - Typo3: Extension selbst erstellen
  4. Lokalisierung und FlexForms - Typo3: Extension selbst erstellen
  5. Planung, Dokumentation und Veröffentlichung – Typo3: Extension selbst erstellen

Verwendung von Templates

Wenn du dich schon etwas mit dem Programmieren auseinandergesetzt hast, kennst du bestimmt schon einige Grundprinzipien, wie zum Beispiel DRY oder die Trennung von Berechnung und Ausgabe. Schaust du dir den Plugin-Code in der Datei pi1/class.tx_lecontactform_pi1 an, sehen wir, dass das HTML-Formular direkt im PHP-Code eingebettet ist. Um nun eine Trennung von PHP und HTML zu erreichen, werden wir Templates benutzen.

Schritt 1: die Template-Datei

Leg zuerst im Ordner pi1/ einen neuen Ordner templates an. In diesem erstellst du dann eine neue HTML-Datei contactform.tmpl. Hier schreiben wir nun das HTML-Formular hinein.


<form action="' . $this->pi_getPageLink($GLOBALS['TSFE']->id) . '" method="POST">
	' . $this->wrapMessage($message, '<div class="' . $class . '">', '</div>') . '
	<p>
		<label for="' . $this->prefixId . '_sender_name">Name:</label>
		<input type="text" id="' . $this->prefixId . '_sender_name" name="' . $this->prefixId . '[sender_name]" placeholder="Ihr Name" required="required" value="' . htmlspecialchars($this->piVars['sender_name']) . '" />
		' . $this->wrapMessage($errors['sender_name'], '<div class="error">', '</div>') . '
	</p>
	<p>
		<label for="' . $this->prefixId . '_sender_mail">E-Mail:</label>
		<input type="email" id="' . $this->prefixId . '_sender_mail" name="' . $this->prefixId . '[sender_mail]" placeholder="Ihre Mail-Adresse" required="required" value="' . htmlspecialchars($this->piVars['sender_mail']) . '" />
		' . $this->wrapMessage($errors['sender_mail'], '<div class="error">', '</div>') . '
	</p>
	<p>
		<label for="' . $this->prefixId . '_message">Nachricht:</label>
		<textarea id="' . $this->prefixId . '_message" name="' . $this->prefixId . '[message]" placeholder="Geben Sie hier Ihre Nachricht an uns ein!" required="required">' . htmlspecialchars($this->piVars['message']) . '</textarea>
		' . $this->wrapMessage($errors['message'], '<div class="error">', '</div>') . '
	</p>
	<p>
		<input type="submit" name="' . $this->prefixId . '[send_message]" value="Nachricht versenden" />
	</p>
</form>

Schritt 2: Platzhalter im Template

Allerdings können wir in das HTML-Template natürlich nicht den PHP-Code schreiben, mit dem wir die Fehlermeldungen oder Ähnliches ausgeben. Dafür gibt es in Typo3 verschiedene Platzhalter, so genannte Marker und Subparts.

Marker sind im Grunde wirklich nur Abstandshalter, die sagen, dass an dieser Stelle etwas eingefügt werden soll. Subparts hingegen umschließen, ähnlich wie zum Beispiel HTML-Tags, einen Abschnitt des Template. Subparts können wir also nicht nur, wie Marker, durch etwas ersetzen, sondern wir können auch den Inhalt des Subparts auslesen. Es wird vielleicht etwas deutlicher, wenn ich es einfach zeige.


<!-- ###CONTACT_FORM### begin -->
<form ###FORM_ATTRIBUTES###>
	###MESSAGE###
	<p>
		<label for="###SENDER_NAME_ID###">###SENDER_NAME_LABEL###:</label>
		<input type="text" id="###SENDER_NAME_ID###" name="###SENDER_NAME_NAME###" placeholder="###SENDER_NAME_PLACEHOLDER###" required="required" value="###SENDER_NAME_VALUE###" />
		###SENDER_NAME_ERROR###
	</p>
	<p>
		<label for="###SENDER_MAIL_ID###">###SENDER_MAIL_LABEL###:</label>
		<input type="email" id="###SENDER_MAIL_ID###" name="###SENDER_MAIL_NAME###" placeholder="###SENDER_MAIL_PLACEHOLDER###" required="required" value="###SENDER_MAIL_VALUE###" />
		###SENDER_MAIL_ERROR###
	</p>
	<p>
		<label for="###MESSAGE_ID###">###MESSAGE_LABEL###:</label>
		<textarea id="###MESSAGE_ID###" name="###MESSAGE_NAME###" placeholder="###MESSAGE_PLACEHOLDER###" required="required">###MESSAGE_VALUE###</textarea>
		###MESSAGE_ERROR###
	</p>
	<p>
		<input type="submit" name="###SEND_NAME###" value="###SEND_LABEL###" />
	</p>
</form>
<!-- ###CONTACT_FORM### end -->

<!-- ###SUCCESS_MESSAGE### begin -->
	<div class="success">
		###MESSAGE###
	</div>
<!-- ###SUCCESS_MESSAGE### end -->

<!-- ###ERROR_MESSAGE### begin -->
	<div class="error">
		###MESSAGE###
	</div>
<!-- ###ERROR_MESSAGE### end -->

<!-- ###VALIDATION_MESSAGE### begin -->
	<div class="validation">
		###MESSAGE###
	</div>
<!-- ###VALIDATION_MESSAGE### end -->

Zuerst schauen wir uns die Subparts an, hier gibt es zwei Subparts. Der eine Subpart geht von Zeile 1 bis Zeile 23 und heißt ###CONTACT_FORM###. Hier siehst du, wie ein Subpart aufgebaut ist. Der Name beginnt und endet grundsätzlich mit ### und ist zusammen mit begin oder end in HTML-Kommentare eingefügt. Der HTML-Code der Zeilen 2 bis 22 ist jetzt mit dem Subpart umschlossen, so können wir diesen Abschnitt hinterher über den Namen ###CONTACT_FORM### auswählen. Weitere Subparts sind übrigens in den Zeilen 25 bis 29 und 31 bis 35 definiert.

Über den ganzen HTML-Code sind Marker verteilt. Einen solchen Marker finden wir als erstes in Zeile 2. Hier heißt der Marker ###FORM_ATTRIBUTES###. Auch Marker beginnen und enden mit ###, allerdings muss man hier weder HTML-Kommenare noch begin oder end schreiben, da die Marker immer nur eine Position markieren, an der etwas eingefügt wird und nicht einen Bereich umschließen wie die Subparts.

Kurz gesagt legen wir uns in dem Template ein Muster an, HTML-Code in den wir mithilfe der Marker und Subparts später die jeweiligen Inhalte einfügen können.

Schritt 3: Verwenden des Templates im Plugin

Nun müssen wir dieses Template noch in unser Plugin einbauen. Schau dir also die Datei class.tx_lecontactform_pi1.php an, hier musst du die Methode main() anpassen. Ganz am Ende der Methode wird bisher in der Variable $content der HTML-Code geschrieben. Diese Zeilen entfernen wir jetzt und fügen stattdessen folgenden PHP-Code ein.


$this->template = $this->cObj->fileResource('EXT:le_contactform/pi1/templates/contactform.tmpl');

$contactFormTemplate = $this->cObj->getSubpart($this->template, '###CONTACT_FORM###');
$successMessageTemplate = $this->cObj->getSubpart($this->template, '###SUCCESS_MESSAGE###');
$errorMessageTemplate = $this->cObj->getSubpart($this->template, '###ERROR_MESSAGE###');
$validationMessageTemplate = $this->cObj->getSubpart($this->template, '###VALIDATION_MESSAGE###');

$content = $this->template;

return $this->pi_wrapInBaseClass($content);

In Zeile 1 wird die Template-Datei ausgelesen und in der Eigenschaft $this->template gespeichert. Danach werden mit der Methode getSubpart() aus dem Template die Subparts ausgelesen und jeweils in einer Variable gespeichert. Schließlich wird der Inhalt des Templates in $content gespeichert und danach zurückgegeben.

Typo3 zeigt nun unser Template mitsamt der Marker an.
Typo3 gibt nun natürlich nur stupide unser Template aus — mitsamt aller Marker.

Schritt 4: Marker ersetzen

So gibt Typo3 unser Template allerdings bloß aus, wir müssen nun dafür sorgen, dass die Marker korrekt ersetzt werden.


$this->template = $this->cObj->fileResource('EXT:le_contactform/pi1/templates/contactform.tmpl');

$contactFormTemplate = $this->cObj->getSubpart($this->template, '###CONTACT_FORM###');
$successMessageTemplate = $this->cObj->getSubpart($this->template, '###SUCCESS_MESSAGE###');
$errorMessageTemplate = $this->cObj->getSubpart($this->template, '###ERROR_MESSAGE###');
$validationMessageTemplate = $this->cObj->getSubpart($this->template, '###VALIDATION_MESSAGE###');

$markerArray = array(
	'###FORM_ATTRIBUTES###'			=> 'class="le_contactform_form" action="' . $this->pi_getPageLink($GLOBALS['TSFE']->id) . '" method="POST"',
	'###MESSAGE###'					=> '',
	'###SENDER_NAME_ID###'			=> $this->prefixId . '_sender_name',
	'###SENDER_NAME_LABEL###'		=> 'Name',
	'###SENDER_NAME_NAME###'		=> $this->prefixId . '[sender_name]',
	'###SENDER_NAME_PLACEHOLDER###'	=> 'Ihr Name',
	'###SENDER_NAME_VALUE###'		=> htmlspecialchars($this->piVars['sender_name']),
	'###SENDER_NAME_ERROR###'		=> '',
	'###SENDER_MAIL_ID###'			=> $this->prefixId . '_sender_mail',
	'###SENDER_MAIL_LABEL###'		=> 'E-Mail',
	'###SENDER_MAIL_NAME###'		=> $this->prefixId . '[sender_mail]',
	'###SENDER_MAIL_PLACEHOLDER###'	=> 'Ihre Mail-Adresse',
	'###SENDER_MAIL_VALUE###'		=> htmlspecialchars($this->piVars['sender_mail']),
	'###SENDER_MAIL_ERROR###'		=> '',
	'###MESSAGE_ID###'				=> $this->prefixId . '_message',
	'###MESSAGE_LABEL###'			=> 'Nachricht',
	'###MESSAGE_NAME###'			=> $this->prefixId . '[message]',
	'###MESSAGE_PLACEHOLDER###'		=> 'Geben Sie hier Ihre Nachricht an uns ein!',
	'###MESSAGE_VALUE###'			=> htmlspecialchars($this->piVars['message']),
	'###MESSAGE_ERROR###'			=> '',
	'###SEND_NAME###'				=> $this->prefixId . '[send_message]',
	'###SEND_LABEL###'				=> 'Nachricht versenden'
);

$content = $this->template;

return $this->pi_wrapInBaseClass($content);

Hier definierst du in dem Array $markerArray, mit was die einzelnen Marker ersetzt werden sollen. Hier fehlen nur noch die (Fehler-)Meldungen, aber diese generieren wir nun dynamisch.


foreach ($errors as $fieldName => $errorText) {
	$validationMarkerArray= array(
		'###MESSAGE###' => $errorText
	);
	
	$markerArray['###' . strtoupper($fieldName) . '_ERROR###'] = $this->cObj->substituteMarkerArrayCached($validationMessageTemplate, $validationMarkerArray);
}

if ($class == 'error') {
	$errorMarkerArray = array(
		'###MESSAGE###' => $message
	);
	
	$markerArray['###MESSAGE###'] = $this->cObj->substituteMarkerArrayCached($errorMessageTemplate, $errorMarkerArray);
} elseif ($class == 'success') {
	$successMarkerArray = array(
		'###MESSAGE###' => $message
	);
	
	$markerArray['###MESSAGE###'] = $this->cObj->substituteMarkerArrayCached($successMessageTemplate, $successMarkerArray);
}

Jeder Error, den wir gespeichert haben, wird hier in das Marker-Array geschrieben, jeweils mit dem richtigen Schlüssel, den wir einfach mit der Funktion strtoupper(), welche den Fehlernamen in Großbuchstaben umwandelt, ermitteln können. Außerdem fügen wir die Nachricht entweder als Success oder als Error ein.

Neu ist hier die Methode substituteMarkerArrayCached(), mit ihr kannst du die Marker in einem Template durch die entsprechenden Inhalte ersetzen. Genau diese Methode benutzen wir nun auch, um die Marker alle in das Template einzufügen.


$content = $this->cObj->substituteMarkerArrayCached($contactFormTemplate, $markerArray);

return $this->pi_wrapInBaseClass($content);

Endlich wird unser Formular wieder so angezeigt, wie es auch sein soll! Und jetzt liegen HTML und PHP schön sauber getrennt voneinander in verschiedenen Dateien!

Formular mit CSS formatieren

Nun sieht das Formular aber noch immer nicht besonders toll aus. Deshalb fügen wir als nächstes eine CSS-Datei hinzu.

Schritt 1: die CSS-Datei

Lege im Ordner pi1/ einen neuen Ordner css an und hier eine neue Datei tx_lecontactform_pi1.css. In dieser Datei können wir später den CSS-Code einfügen. Doch erstmal müssen wir die Datei natürlich wieder in unser Plugin einfügen.

Schritt 2: Datei in das Plugin einfügen

Um die CSS-Datei in das Plugin zu laden, fügen wir folgende Zeile direkt nach den Aufruf von $this->pi_loadLL();.


$GLOBALS['TSFE']->pSetup['includeCSS.'][$this->extKey] = 'EXT:' . $this->extKey . '/pi1/css/tx_lecontactform_pi1.css';

Die CSS-Datei ist jetzt ins Plugin eingebunden und wird von Typo3 automatisch an den Browser ausgeliefert. Auf der nächsten Seite testen wir deshalb unsere CSS-Datei.

Schritt 3: CSS anpassen

Nun können wir mithilfe von CSS in der Datei pi1/css/tx_lecontactform_pi1.css das Formular stylen.


.le_contactform_form label {
	display: block;
}

.le_contactform_form input[type=text], .le_contactform_form input[type=email], .le_contactform_form textarea {
	width: 100%;
	
	padding: 5px 8px;
}

.le_contactform_form input, .le_contactform_form textarea {
	font-family: Verdana, Geneva, sans-serif;
	font-size: 1em;
}

.le_contactform_form textarea {
	height: 150px;
	
	text-align: justify;
	
	resize: vertical;
}

.le_contactform_form .success, .le_contactform_form .error, .le_contactform_form .validation {
	border: 1px solid;
    margin: 10px 0px;
    padding:15px 10px 15px 50px;
    background-repeat: no-repeat;
    background-position: 10px center;
}

.le_contactform_form .success {
    color: #4F8A10;
    background-color: #DFF2BF;
    background-image: url('../images/accept.png');
}

.le_contactform_form .error {
    color: #D8000C;
    background-color: #FFBABA;
    background-image: url('../images/cancel.png');
}

.le_contactform_form .validation {
	color: #D63301;
	background-color: #FFCCBA;
	background-image: url('../images/exclamation.png');
}

(Quelle für .success, .error und .validation: CSS Message Boxes for different message types - Janko at Warp Speed)

Übrigens habe ich auch noch drei Icon-Dateien zu dem pi1-Ordner hinzugefügt, was aber eher Design-Gründe hat. Hier muss man bezüglich Typo3 nichts beachten, ich habe die Icons einfach per CSS eingebunden.

Das Formular hat jetzt ein Design.
Jetzt sieht das Formular doch gleich viel besser aus. Was doch ein paar Zeilen CSS-Code ausmachen können…

Konfiguration des Plugins über TypoScript

Jetzt kommt vielleicht die Frage auf, wieso wir das Template so ausführlich und ausgiebig mit Markern versehen haben. Das wäre doch auch einfacher gegangen, wenn wir vieles direkt ins Template geschrieben hätten, wie zum Beispiel Namen oder IDs. Dies hatte allerdings einen Grund, denn im folgenden Schritt geben wir dem Seitenbetreiber die Möglichkeit, eine eigene Template-Datei zu erstellen und diese von der Extension verwenden zu lassen. So kann der Seitenbetreiber die Extension relativ einfach an das Design der eigenen Seite anpassen. Damit sich der Designer jedoch nicht mit den IDs oder den Namen herumschlagen muss, reicht es, hier den Marker zu benutzen. Sollten sich die Namen oder IDs aus irgendeinem Grund irgendwann mal ändern, funktionieren die fremden Templates immer noch.

Nun aber genug geredet, schauen wir uns einmal an, wie wir die TypoScript-Konfiguration nun realisieren.

Schritt 1: neues Test-Template

Um das Definieren eines eigenen Templates zu testen, kopiere ich die Datei contactform.tmpl in den Ordner /fileadmin/templates/main/. Anschließend ändere ich das Template ein wenig, indem ich am Anfang nur einen kurzen Text einfüge. Das dient nur dazu, hinterher auch ein Ergebnis zu sehen. Der Seitenbetreiber kann hinterher das Template an dieser Stelle beliebig modifizieren, aber uns reicht an dieser stelle nur eine kleine Änderung.


<!-- ###CONTACT_FORM### begin -->
<form ###FORM_ATTRIBUTES###>
	<p>
		Dies ist mein geändertes Kontaktformular!
	</p>
	###MESSAGE###

Nun begeben wir uns ins Typo3-Backend in das Modul »Template« und wählen das Haupt-Template der Seite aus. Hier fügen wir dann zu Beginn des Setups folgendes TypoScript hinzu.


plugin.tx_lecontactform_pi1.template = fileadmin/templates/main/contactform.tmpl

Schritt 2: TypoScript-Variable abfragen

Auf diese Weise können die Typo3-Nutzer später den Pfad zu einem eigenen Template definieren. Doch momentan passiert nichts, denn wir müssen als nächstes erst diese Konfigurations-Variable abfragen. Dazu begeben wir uns wieder in unser Plugin in class.tx_lecontactform_pi1.php.

Hier fügen wir nach dem Aufruf von $this->pi_loadLL(); folgenden Code hinzu.


if($this->conf['template'] == '') {
	$this->conf['template'] = 'EXT:' . $this->extKey . '/pi1/templates/contactform.tmpl';
}

Typo3 sorgt automatisch dafür, dass in der Variable $this->conf alle Variablen landen, die der Nutzer über das TypoScript-Setup definiert hat. Hier finden wir deshalb in der Variable $this->conf['template'] == '' den eben definierten Pfad zum neuen Template — zumindest, wenn der Nutzer einen Pfad angegeben hat. Wenn nicht, ist diese Variable leer. Und in genau diesem Fall müssen wir unser Standard-Template in die Variable schreiben.

Weiter unten, wenn wir das Tempate in die Variable $this->template schreiben, müssen wir nun nicht mehr manuell den Pfad angeben, sondern können über $this->conf['template'] darauf zugreifen.


$this->template = $this->cObj->fileResource($this->conf['template']);
Im Kontaktformular wird jetzt unser eigenes Template verwendet!
Der im alternativen Template eingefügte Text erscheint, es funktioniert!

Analog können wir dem Nutzer auch die Möglichkeit geben, ein eigenes Stylesheet zu geben. Dazu ändern wir wieder unsern Code ein wenig. Nach $this->pi_loadLL(); muss nun folgender Code stehen.


if($this->conf['template'] == '') {
	$this->conf['template'] = 'EXT:' . $this->extKey . '/pi1/templates/contactform.tmpl';
}
if($this->conf['cssFile'] == '') {
	$this->conf['cssFile'] = 'EXT:' . $this->extKey . '/pi1/css/tx_lecontactform_pi1.css';
}

$GLOBALS['TSFE']->pSetup['includeCSS.'][$this->extKey] = $this->conf['cssFile'];

Nun kann der Nutzer auch ein eigenes CSS-File definieren.

Es ist also ganz einfach, dem Nutzer eigene Konfigurations-Möglichkeiten anzubieten. Man muss lediglich den entsprechenden Wert aus $this->conf überprüfen und notfalls mit dem Standard-Wert auffüllen. So können wir dem Nutzer über TypoScript beliebige Konfigurations-Variable an die Hand geben und die Flexibilität weiter erhöhen.

Bereit für das Finale? Auf der nächsten und letzten Seite des Artikels ziehen wir aus dem Gelernten noch ein Fazit. Außerdem gibt es einen Ausblick, wie es weiter geht!

Fazit: flexible Extensions

Mit Typo3 lassen sich sehr flexible Extensions programmieren, die Mithilfe von eigenen CSS- und Template-Dateien über TypoScript angepasst werden können. Aber an diesem Punkt wollen wir noch nicht Halt machen. Im nächsten Teil der Artikelserie wirst du zum Beispiel lernen, wie man in Typo3 Extensions programmiert, die mehrere Sprachen sprechen und sich einfach in andere Sprachen übersetzen lassen.

Wie findest du die Möglichkeiten, die Typo3 uns an die Hand gibt? Für mich sind Extensions sehr nützlich, denn so kann man schnell und flexibel alle möglichen Anforderungen oder Funktionen für Typo3 entwickeln und immer wieder verwenden.

Nächster Artikel der Serie

Dieser Artikel ist Teil der Artikelserie »Typo3: Extension selbst erstellen«.

Hier geht es zum nächsten Artikel der Serie: Lokalisierung und FlexForms - Typo3: Extension selbst erstellen