Erste Erfahrungen mit TYPO3 7.6.

Inspiriert von dem TYPO3 Camp in Essen beschloss ich gleich beim nächsten Projekt TYPO3 7.6. in Kombination mit Fluid Styled Content zu verwenden und sowieso ein paar Dinge anders zu machen als vorher. Am Anfang war es etwas frustrierend, da manche Dinge noch etwas buggy waren oder anders funktionierten als bisher. Zum Beispiel arbeite ich gerne mit Flexforms in Kombination mit Inline-Elementen. Eine Slideshow z.B.: Flexform, Slides als Inline-Datensätze und voilà: einfach für die Redakteure, da alles schön am gleichen Ort ist. Leider funktioniert das Vorgehen mit TYPO3 7.6. nicht (oder zumindest nicht in 7.6.0). Da musste ich auf die klassische Methode ausweichen: Datensätze in Storagefolder und dann auslesen.

Sehr cool ist allerdings Fluid Styled Content. So kann man z.B. eigene Inhaltselemente definieren (Beispiel hier: http://stackoverflow.com/questions/32993534/with-fluid-styled-content-how-to-create-custom-content-elements-in-typo3-7-5) oder das Rendering der Elemente ohne TypoScript nur in Fluid Templates anpassen. Sehr elegant!

Beim Camp wurde auch die Extension mask vorgestellt. Anfang Dezember lief sie noch nicht, aber seit dem Update kurz vor Weihnachten kann man sie auch benutzen. Erster Eindruck: sehr schick! Mit der Extension lassen sich eigene Inhaltselemente definieren (wie bei DCE). Mask erstellt jedoch für diese Inhaltselemente neue Tabellen und Tabellenspalten und (angeblich) bleiben die Inhaltselemente auch erhalten, wenn man Mask deinstalliert. Weiterer grosser Unterschied und Vorteil gegenüber DCE ist, dass man die Elemente mit Mask versionieren kann. Bei DCE ist es zwar möglich die Templates in Dateien auszulagern, aber diese Dateien müssen in fileadmin liegen. Wenn ich also alles in einer Theme-Extension hinterlegt habe, habe ich keine Möglichkeit im Formular im DCE die entsprechenden Templates auszuwählen. Weiterhin müssen in der Entwicklungsumgebung angelegte DCE-Elemente in der Live-Umgebung wieder erstellt werden (gibt es noch andere Möglichkeiten außer SQL-Dump?). Bei Mask wird die Konfiguration in einer json-Datei gespeichert, die Templates werden automatisch aus konfigurierbaren Ordnern ausgelesen. Somit kann man in der Entwicklungsumgebung Mask-Elemente erstellen, dann alles auf den Live-Server spielen, die Mask-Elemente erneut speichern (damit Datenbank aktualisiert wird) und fertig. Habe bisher noch nicht getestet, wie es mit Bildern und Dateien klappt, ansonsten ist Mask bisher sehr vielversprechend.

Fazit: TYPO3 macht Spaß und mit der Version 7.6. noch mehr!

Suchergebnisse indexed_search dynamisch nachladen

Um die Paginierung auszublenden und die Inhalte der nächsten Seiten nachzuladen gibt es für die News-Extension einige Beispiele und sogar ein paar Plugins. Für indexed_search habe ich nichts dergleichen gefunden (Links können gerne in den Kommentaren gepostet werden). Zugegebenermaßen ist es etwas komplizierter als bei News, vor allem deswegen, weil das Template nicht so flexibel ist, aber machbar ist es schon.

Als erstes braucht man eine Seite, die Ajax-Abfragen verarbeitet, das ist für die Suche relativ einfach:

search = PAGE
search {
	typeNum = 1981
	config {
		disableAllHeaderCode = 1
		xhtml_cleaning = none
		admPanel = 0
		metaCharset = utf-8
		additionalHeaders = Content-Type:text/html;charset=utf-8
		disablePrefixComment = 1
	}
	10 < plugin.tx_indexedsearch
}

Nun braucht man einen Button, der bei Klick die Ergebnisse nachlädt. Leider finden sich nicht das komplette HTML der indexed_search im Template. Einiges wird über TypoScript generiert, manche Wrapper werden im PHP-Code einfach gesetzt. Den Button könnte man bestimmt auch mit jQuery generieren, ich habe ihn mittels stdWrap nach den Results eingefügt:

plugin.tx_indexedsearch {
	resultlist_stdWrap.wrap = |<button type="button" class="load-more expand secondary"><i class="fa fa-refresh"></i> {$labelMoreSearchResults}</button>
}

Dann muss man nur noch mit jQuery den Pagebrowser ausblenden und die Ergebnisse bei Klick auf Button nachladen. In diesem Skript sind ein paar Dinge umschön. Zum einen werden bei Indexed Search die Parameter nicht an die Links in der Paginierung angehängt. Bei Klick auf eine Seite wird mit JavaScript ein Parameter im Formular gesetzt und das Suchformular erneut abgeschickt. Im Skript unten mache ich es ähnlich. Zum anderen wird bei der Paginierung am Ende an Link zur nächsten Seiten generiert mit „>>“. Somit sind mehr Links in der Paginierung als Seiten, daher die Abfrage pagesLoaded == paginationPages - 1. Man müsste schauen, ob dieser Code-Schnipsel dadurch ggf. für einzelne Projekte angepasst werden muss.

var pagesLoaded = 0;
var paginationPages;
 
function loadMoreResults() {
	if($('.tx-indexedsearch-browsebox').eq(0).find('ul.browsebox').length < 1) {
		// Button ausblenden wenn keine Paginierung
		$('.tx-indexedsearch-res .load-more').hide();
		return;
	}
	// Paginierung ausblenden
	$('.tx-indexedsearch-browsebox ul.browsebox').hide();
 
	var button = $('.tx-indexedsearch-res .load-more');
 
	var paginationLinks = $('.tx-indexedsearch-browsebox').eq(1).find('ul.browsebox li:not([class])');
	paginationPages = paginationLinks.length;
 
	// Button Event & Ajax-Request
	button.not('.disabled').on('click', function(e){
		e.preventDefault();
 
		if(button.hasClass('disabled')){
			return;
		}
 
		if(pagesLoaded == paginationPages - 1) {
			return;
		}
 
		$('#tx_indexedsearch_pointer').val(pagesLoaded + 1);
		$('#tx_indexedsearch_freeIndexUid').val('-1');
 
		$.ajax({
			async: 'true',
			url: window.location.href + '?type=1981',
			data: $('#tx_indexedsearch').serialize(),
			type: 'POST',
			dataType: 'html',
			success: function (data) {
				pagesLoaded++;
				$data = $(data);
				var insertContent = $data.find('.result-row');
				var insertAfter = $('.tx-indexedsearch-res .result-row').last();
				insertAfter.after(insertContent);
 
				button.removeClass('disabled');
				// last page in pagination is >>, skip that
				if(pagesLoaded == paginationPages - 1) {
					button.remove();
				}
			},
			error: function (error) {
				button.removeClass('disabled');
			}
		});
	});
}
 
$(document).ready(function() {
	loadMoreResults();
});

Geschrieben in TYPO3 | Kommentare deaktiviert für Suchergebnisse indexed_search dynamisch nachladen

Coole Radio-Buttons und Checkboxen ohne JavaScript

Man braucht nur ein zusätzliches Span im Label (wahrscheinlich nicht mal das) und muss darauf achten, dass das for-Attribut beim Label korrekt gesetzt ist.

Das HTML:

<input id="radiobutton" type="radio" name="radiobutton" value="1" />
<label for="radiobutton"><span></span>Radio Button</label>

Man blendet die eigentliche Box aus und „zeichnet“ stattdessen eine Box mit CSS. Wenn die Box angeklickt ist, dann mache ich in dem Fall ein blaues Quadrat rein, man könnte aber auch Iconfont nehmen oder doch Hintergrundbilder (die ich eigentlich damit vermeiden will).

Das Styling als SCSS:

input[type="radio"] {
	display:none;
}
 
input[type="radio"] + label span {
	display:inline-block;
	position: relative;
	width:22px;
	height:22px;
	background-color: $darkgray;
	border: 2px solid #ababab;
	@include border-radius(3px);
	margin-right: 5px;
	cursor:pointer;
}
 
input[type="radio"]:checked + label span {
	&::after {
		content: "";
		@include border-radius(3px);
		width: 10px;
		height: 10px;
		background-color: $blue;
		border: 1px solid darken($blue, 10%);
		position: absolute;
		top: 3px;
		left: 3px;
		display: block;
	}
}

Das gleiche Prinzip lässt sich auch auf Checkboxen übertragen.

Geschrieben in css | Kommentare deaktiviert für Coole Radio-Buttons und Checkboxen ohne JavaScript

TYPO3: ViewHelper für Foundation Interchange

Foundation Interchange ist eine Technik, bei der Media Queries verwendet werden, um Inhalte resonsiv zu laden. Ok, vielleicht klingt es auf englisch besser: „Interchange uses media queries to dynamically load responsive content that is appropriate for different users‘ browsers.“

Da ich Foundation einsetze und die Interchange Technik super finde, dachte, dass es an der Zeit ist, einen View Helper für Bilder zu schreiben. Bisher habe ich es nur im Template gelöst:

<img data-interchange="[{f:uri.image(image: image, width: settings.imageWidth)}, (default)], [{f:uri.image(image: image, maxWidth: settings.imageWidth)}, (large)]" />

Leider hat diese Technik einen entscheidenden Nachteil – die alt und title-Attribute des Bildes werden nicht ausgegeben. Wenn man diese abhängig vom Bild ausgeben lassen würde, bräuchte man einen ViewHelper dafür.

Ich habe einen ViewHelper entwickelt, der von ImageViewHelper in Fluid ableitet und fast genauso funktioniert. Mit einem kleinen Unterschied: anstatt die Bildgröße direkt zu übergeben, übergibt man eine Interchange-Konfiguration. Und das ist der ViewHelper:

<?php
namespace My\Extension\ViewHelpers;
 
class ImageInterchangeViewHelper extends \TYPO3\CMS\Fluid\ViewHelpers\ImageViewHelper {
 
	/**
	 * Resizes a given image (if required) and renders the respective img tag
	 *
	 * @see http://typo3.org/documentation/document-library/references/doc_core_tsref/4.2.0/view/1/5/#id4164427
	 * @param string $src a path to a file, a combined FAL identifier or an uid (integer). If $treatIdAsReference is set, the integer is considered the uid of the sys_file_reference record. If you already got a FAL object, consider using the $image parameter instead
	 * @param array $interchangeSettings
	 * @param boolean $treatIdAsReference given src argument is a sys_file_reference record
	 * @param FileInterface|AbstractFileFolder $image a FAL object
	 *
	 * @throws \TYPO3\CMS\Fluid\Core\ViewHelper\Exception
	 * @return string Rendered tag
	 */
	public function render($src = NULL, $interchangeSettings = NULL, $treatIdAsReference = FALSE, $image = NULL) {
		if (is_null($src) && is_null($image) || !is_null($src) && !is_null($image)) {
			throw new \TYPO3\CMS\Fluid\Core\ViewHelper\Exception('You must either specify a string src or a File object.', 1382284106);
		}
		$image = $this->imageService->getImage($src, $image, $treatIdAsReference);
		$interchangeData = array();
		foreach($interchangeSettings as $key => $settings) {
			$processingInstructions = array(
				'width' => $settings['width'],
				'height' => $settings['height'],
				'minWidth' => $settings['minWidth'],
				'minHeight' => $settings['minHeight'],
				'maxWidth' => $settings['maxWidth'],
				'maxHeight' => $settings['maxHeight'],
			);
			$processedImage = $this->imageService->applyProcessingInstructions($image, $processingInstructions);
			$imageUri = $this->imageService->getImageUri($processedImage);
 
			$interchangeData[] = '['.$imageUri.', ('.$key.')]';
		}
 
		$this->tag->addAttribute('data-interchange', implode(',', $interchangeData));
 
		$alt = $image->getProperty('alternative');
		$title = $image->getProperty('title');
 
		if (empty($this->arguments['alt'])) {
			$this->tag->addAttribute('alt', $alt);
		}
		if (empty($this->arguments['title']) && $title) {
			$this->tag->addAttribute('title', $title);
		}
 
		return $this->tag->render();
	}
 
}

Und so kann man ihn verwenden:

<my:imageInterchange image="{image}" interchangeSettings="{small: {width: 400}, medium: {width: 800}, default: {width: 1200}}" />

Geschrieben in TYPO3 | Kommentare deaktiviert für TYPO3: ViewHelper für Foundation Interchange

TYPO3: Externe Links in Menü mit Klasse versehen

Damit man die externen Links in einer Navigation auch als solche erkennen kann (mit CSS ein Icon davor klatschen), müssen die entsprechenden Seiten in der Navigation mit einer Klasse versehen werden. Das ist der Code dafür:

lib.navigation = HMENU
lib.navigation {
	special = directory
	special.value = {$rootPage}
	1 = TMENU
	1 {
		expAll = 1
		wrap = <ul>|</ul>
 
		NO = 1
		NO {
			wrapItemAndSub = <li>|</li>
			ATagParams.stdWrap.cObject = TEXT
			ATagParams.stdWrap.cObject {
				value = class="external"
				if.equals = 3
				if.value.field = doktype
			}
		}
		[...]
	}
}

Geschrieben in TYPO3 | Kommentare deaktiviert für TYPO3: Externe Links in Menü mit Klasse versehen

TYPO3: FlexForm Conditions verknüpfen

Ein Teil vom FlexForm soll nur angezeigt werden, wenn z.B. eine bestimmte Action ausgewählt ist (der Klassiker):

<settings.whatever> 
	<TCEforms> 
		<label>Das ist das Feld</label> 
		<displayCond>FIELD:switchableControllerActions:=:Event->list;</displayCond>
		<config>
		</config>
	</TCEforms>
</settings.whatever>

Wenn das Feld angezeigt werden soll, wenn eine von mehreren Actions ausgewählt ist? Dann so:

<settings.whatever> 
	<TCEforms> 
		<label>Das ist das Feld</label> 
		<displayCond>
			<OR>
				<numIndex index="0">FIELD:switchableControllerActions:=:Event->list;</numIndex>
				<numIndex index="1">FIELD:switchableControllerActions:=:Event->teaser;</numIndex>
			</OR>
		</displayCond>
		<config>
		</config>
	</TCEforms>
</settings.whatever>

Wichtig ist, dass bei numIndex jeweils ein Index angegeben ist. Danke an diesen Blogpost hier: Flexform displayCond with OR relation.

Geschrieben in TYPO3 | Kommentare deaktiviert für TYPO3: FlexForm Conditions verknüpfen

Welches Stylesheet greift denn nun?

Das Framework Foundation hat mehrere Breakpoints für Responsive Websites. Vielleicht wissen manche Frontend-Entwickler immer sofort, in welchem Abschnitt (zwischen welchen Breakpoints) sie sich nun befinden. Mir fällt es manchmal schwer, das auf den ersten Blick zu erkennen – ist das schon large oder noch medium. Und so habe ich ein CSS-Schnipsel, das ohne den Code der Seite zu verändern in der oberen Ecke anzeigt, in welche Kategorie die aktuelle Auflösung eingeordnet wird.

body:after {
	content: "other";
	position: fixed;
	top: 0;
	left: 0;
	background-color: white;
	border: 1px solid silver;
	color: silver;
	z-index: 100;
	padding: 10px;
}
@media #{$large-only} {
	body:after {
		content: "large";
	}
}
@media #{$medium-only} {
	body:after {
		content: "medium";
	}
}
@media #{$small-only} {
	body:after {
		content: "small";
	}
}

Geschrieben in css | Kommentare deaktiviert für Welches Stylesheet greift denn nun?

TYPO3: Foundation und Form-Extension

Bereits seit einiger Zeit nutzt ich Foundation als Basis für meine Projekte. Da ich für ein einfaches Mail-Formular nicht gleich Powermail oder Formhandler nehmen wollte, wollte ich die Ausgabe des Formulars an den HTML-Code von Foundation anpassen, damit die Styles direkt greifen. Das hat auch fast geklappt (siehe unten).
Es ist nicht möglich, den contrainerWrap zu entfernen. Sobald ich nur noch stehen blieb, war das Formular komplett verschwunden, d.h. irgendwo wird doch ein Wrapper erwartet. Ganz ohne CSS-Anpassungen kommt man dann doch nicht aus, aber diese sind überschaubar.

Weiterhin finde ich es doof, dass Pflichtfelder nicht mit einem required-Attribut versehen werden. Und bei der Validierung sollte das Label und das Feld ebenfalls mit der Klasse „error“ versehen werden. Nach ein paar Stunden Recherche im Code von Form habe ich es schließlich aufgegeben und hoffe, dass tx_form bald auch auf Fluid-Templates umgestellt wird.

SASS:

.csc-mailform {
	.csc-form-element {
		margin-bottom: rem-calc(10);
	}
	.csc-form-element-submit input {
		@include button;
	}
	span.error {
		margin-bottom: 0;
	}
	input, select, textarea {
		margin-bottom: 0;
	}
}

TypoScript:

# Layout des Mailform anpassen #
tt_content.mailform.20.form {
	layout {
		containerWrap (
			<div class="form">
				<elements />
			</div>
		)
		elementWrap (
			<div class="row">
				<element />
			</div>
		)
		label (
			<label>
				<labelvalue />
				<mandatory />
			</label>
		)
		textblock (
			<div class="large-12 medium-12 columns">
				<textblock />
			</div>
		)
		textline (
			<div class="large-4 medium-4 columns">
				<label />
			</div>
			<div class="large-8 medium-8 columns">
				<input />
				<error />
			</div>
		)
		textarea (
			<div class="large-4 medium-4 columns">
				<label />
			</div>
			<div class="large-8 medium-8 columns">
				<textarea />
				<error />
			</div>
		)
		select (
			<div class="large-4 medium-4 columns">
				<label />
			</div>
			<div class="large-8 medium-8 columns">
				<select>
					<elements />
				</select>
				<error />
			</div>
		)
		submit (
			<div class="large-offset-4 medium-offset-4 large-8 medium-8 columns">
				<label />
				<input />
			</div>
		)
		error (
			<span class="error">
				<errorvalue />
			</span>
		)
	}
}

TYPO3: ViewHelper für Frontend-Links aus Scheduler-Tasks

Vor ein paar Monaten habe ich bereits darüber geschrieben, wie man aus z.B. einem Task, der E-Mails versendet, TYPO3 konform Links ins Frontend generieren kann. Nun habe ich das ganze in einen ViewHelper ausgelagert, damit man noch eleganter Links in Standalone-Templates generieren kann.

Hinwein: ich musste den Vendor und den Extensionnamen ändern, daher ist das nicht der tatsächlich funktionierende Code. Wenn es in der eigenen Extension nicht auf Anhieb klappt, dann Vendor und Extensionname anpassen.

Aufruf im Mail-Template:

{namespace my = My\Extension\ViewHelper}
[...]
<p><my:fe.link pageUid="{targetPageUid}" pluginName="Event" pluginArguments="{event: event}">zur Webseite mit Parameter</my:fe.link></p>
[...]

Und das ist der ViewHelper, abzulegen in ViewHelper/Fe/LinkViewHelper.php:

<?php
<?php
namespace My\Extension\ViewHelper\Fe;
 
class LinkViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper {
 
	/**
	 * @var string
	 */
	protected $tagName = 'a';
 
	/**
	 * Arguments initialization
	 *
	 * @return void
	 */
	public function initializeArguments() {
		$this->registerUniversalTagAttributes();
		$this->registerTagAttribute('name', 'string', 'Specifies the name of an anchor');
		$this->registerTagAttribute('rel', 'string', 'Specifies the relationship between the current document and the linked document');
		$this->registerTagAttribute('rev', 'string', 'Specifies the relationship between the linked document and the current document');
		$this->registerTagAttribute('target', 'string', 'Specifies where to open the linked document');
	}
 
	/**
	 * @param int $pageUid
	 * @param string $pluginName
	 * @param array $pluginArguments
	 * @return string content
	 */
	public function render($pageUid, $pluginName = NULL, $pluginArguments = NULL) {
		$this->initTSFE();
		$cObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\ContentObject\\ContentObjectRenderer');
 
		$additionalParams = '';
		if(isset($pluginArguments) && is_array($pluginArguments) && isset($pluginName)) {
			foreach($pluginArguments as $key => $value) {
 
				$namespace = \TYPO3\CMS\Core\Utility\GeneralUtility::camelCaseToLowerCaseUnderscored($pluginName);
				if($value instanceof \TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject) {
					if ($value->getUid() !== NULL) {
						$additionalParams.= '&tx_extension_'.$namespace.'['.$key.']='.$value->getUid();
					}
				} else {
					$additionalParams.= '&tx_extension_'.$namespace.'['.$key.']='.$value;
				}
			}
		}
 
		$uri = $cObj->typolink_URL(array('parameter' => $pageUid, 'additionalParams' => $additionalParams, 'forceAbsoluteUrl' => 1));
 
		$this->tag->addAttribute('href', $uri, FALSE);
		$this->tag->setContent($this->renderChildren());
		$this->tag->forceClosingTag(TRUE);
		return $this->tag->render();
	}
 
	protected function initTSFE($id = 1, $typeNum = 0) {
		\TYPO3\CMS\Frontend\Utility\EidUtility::initTCA();
		if (!is_object($GLOBALS['TT'])) {
			$GLOBALS['TT'] = new \TYPO3\CMS\Core\TimeTracker\NullTimeTracker;
			$GLOBALS['TT']->start();
		}
		$GLOBALS['TSFE'] = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\Controller\\TypoScriptFrontendController',  
			$GLOBALS['TYPO3_CONF_VARS'], $id, $typeNum);
		$GLOBALS['TSFE']->connectToDB();
		$GLOBALS['TSFE']->initFEuser();
		$GLOBALS['TSFE']->determineId();
		$GLOBALS['TSFE']->initTemplate();
		$GLOBALS['TSFE']->getConfigArray();
 
		if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('realurl')) {
			$rootline = \TYPO3\CMS\Backend\Utility\BackendUtility::BEgetRootLine($id);
			$host = \TYPO3\CMS\Backend\Utility\BackendUtility::firstDomainRecord($rootline);
			$_SERVER['HTTP_HOST'] = $host;
		}
	}
}
?>

Geschrieben in TYPO3 | Kommentare deaktiviert für TYPO3: ViewHelper für Frontend-Links aus Scheduler-Tasks

Datum in Sublime umformatieren

Für ein Projekt brauchte ich ein paar Demo-User, die ich mir mit dem Fake Name Generator habe erstellen lassen. Das Format für Geburtsdatum in der Datei ist dd/mm/yyyy, meine Datenbank erfordert aber yyyy-mm-dd. Da ich mit Sublime entwickele und es dort die Möglichkeit gibt, Suchen und Ersetzen auch mit regulären Ausdrücken durchzuführen, kann man es so machen.

Suchen und Ersetzen, dann RegEx aktivieren.
Find What: (\d{1,2})/(\d{1,2})/(\d{4})
Replace With: ${3}-${2}-${1}

Geschrieben in default | Kommentare deaktiviert für Datum in Sublime umformatieren