Wir haben oft die Herausforderung, dass wir über das TYPO3 Backend schnell die Spaltenaufteilung für diverse Elemente festlegen bzw. ändern wollen. Hierbei kann es sich um einfache Inhaltselemente oder auch Gridelemente handeln.
Wie das ganze aussehen kann, seht ihr hier:
Die Methode als eigener renderType bietet euch die Möglichkeit, den Editor für diverse Felder zu definieren. Im Video-Beispiel sehen wir, dass für drei verschiedene Bildschirm-Auflösungen Grid-Editoren vorhanden sind. Wie gehen wir jetzt vor:
- Konfiguration des "grid"-Feldes
- Registrieren und Erstellen des renderTypes
- Einbinden der Javascript-Libraries
- Anpassen der Fluid-Templates
Konfiguration des "grid"-Feldes
Der neue renderType kann ganz einfach für TCA Felder oder auch FlexForm Felder verwendet werden. Über die parameters können wir die definierten Bereiche areas und Spalten des Grids cols angeben (in diesem Fall 12 Spalten und 1 Bereich)
TCA-Konfiguration:
'grid' => [
'exclude' => 1,
'label' => 'LLL:EXT:gridexample/Resources/Private/Language/locallang_db.xlf:grid',
'config' => [
'type' => 'user',
'renderType' => 'gridWizardElement',
'parameters' => [
'cols' => 12,
'areas' => 1
]
]
]
Konfiguration als FlexForm-Feld:
<grid>
<TCEforms>
<label>LLL:EXT:gridexample/Resources/Private/Language/locallang_db.xlf:grid</label>
<config>
<type>user</type>
<renderType>gridWizardElement</renderType>
<parameters>
<cols>12</cols>
<areas>2</areas>
</parameters>
</config>
</TCEforms>
</grid>
Registrieren und Erstellen des renderTypes
Um den eigenen renderType verwenden zu können, muss er in der ext_localconf.php eures Site Packages oder einer beliebigen Extension registriert werden:
$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1557817723] = [
'nodeName' => 'gridWizardElement',
'priority' => 40,
'class' => \Varioous\Gridexample\Form\Element\GridWizardElement::class,
];
Wunderbar! Jetzt erstellen wir unser Formengine Element unter dem Pfad Classes/Form/Element/GridWizardElement.php. Dazu folgendes vorweg: im Array $areas wird die fertige Konfiguration des Feldes gespeichert. $colCount und $areaCount stehen für unsere definierten Parameter. $colCount gibt die Anzahl an Spalten des Grids an, $areaCount die Anzahl der Bereiche, die wir im Grid verändern können. $fieldName beinhaltet den Namen des TCA- oder FlexForm-Feldes.
<?php
namespace Varioous\Gridexample\Form\Element;
...
class GridWizardElement extends AbstractFormElement
{
/**
* @var array
*/
protected $areas = [];
/**
* @var int
*/
protected $colCount = 12;
/**
* @var int
*/
protected $areaCount = 1;
/**
* @var int
*/
protected $rowCount = 1;
/**
* @var string
*/
protected $fieldName = '';
In der render-Methode bauen wir den HTML-Code des Feldes zusammen und definieren unser Javascript-Modul (näheres dazu später):
/**
* @return array
*/
public function render() {
...
$json = json_encode($this->areas, JSON_HEX_QUOT | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS);
$html = [];
$html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
$html[] = $fieldInformationHtml;
$html[] = '<div class="form-control-wrap">';
$html[] = '<div class="form-wizards-wrap">';
$html[] = '<div class="form-wizards-element">';
$html[] = '<input';
$html[] = ' type="hidden"';
$html[] = ' name="' . htmlspecialchars($this->data['parameterArray']['itemFormElName']) . '"';
$html[] = ' value="' . htmlspecialchars($this->data['parameterArray']['itemFormElValue']) . '"';
$html[] = '/>';
$html[] = '<style>#grideditor-' . $this->fieldName . ' *,
#grideditor-' . $this->fieldName . ' *::after,
#grideditor-' . $this->fieldName . ' *::before {
box-sizing: inherit;
}
#grideditor-' . $this->fieldName . ' {
box-sizing: border-box;
}
</style>';
$html[] = '<div id="grideditor-' . $this->fieldName . '"';
$html[] = ' data-field="' . htmlspecialchars($this->data['parameterArray']['itemFormElName']) . '"';
$html[] = ' data-areas="' . htmlspecialchars($json) . '"';
$html[] = ' data-rowcount="' . (int)$this->rowCount . '"';
$html[] = ' data-colcount="' . (int)$this->colCount . '"';
$html[] = ' data-areacount="' . (int)$this->areaCount . '">';
$html[] = '</div>';
$html[] = '</div>';
$html[] = '<div class="form-wizards-items-bottom">';
$html[] = $fieldWizardHtml;
$html[] = '</div>';
$html[] = '</div>';
$html[] = '</div>';
$html[] = '</div>';
$html = implode(LF, $html);
$resultArray['html'] = $html;
$resultArray['requireJsModules'][] = ['TYPO3/CMS/Gridexample/grideditor/GridEditor' => 'function(GridEditor) { new GridEditor("' . htmlspecialchars($this->fieldName) . '") } '];
return $resultArray;
}
In der init-Methode wird überprüft, ob beim Aufruf des renderTypes bereits ein Wert in der Datenbank steht und dieser gegebenenfalls ausgelesen. Weiters können die Default-Values für die GridEditor-Felder per Page TSconfig gesetzt werden.
/**
* Initialize wizard
*/
protected function init() {
...
// TS parser
$parser = GeneralUtility::makeInstance(TypoScriptParser::class);
if($dbValue){
// load TS parser
$parser->parse($dbValue);
$data = $parser->setup['grid.'];
$areas = $data['areas.'];
if(count($areas) != (int)$this->areaCount) {
$areasDoNotMatchAreaCount = true;
}
} else {
$pageTsConfig = BackendUtility::getPagesTSconfig($this->data['effectivePid']);
if($isFlexform) {
$tsConfigValue = $pageTsConfig['TCAdefaults.'][$this->data['tableName'] . '.'][$this->data['fieldName'] . '.']['data.'][$this->data['flexFormSheetName'] . '.'][$this->data['flexFormFieldName'] . '.'][$this->data['databaseRow']['CType'][0]];
} else {
$tsConfigValue = $pageTsConfig['TCAdefaults.'][$this->data['tableName'] . '.'][$this->data['fieldName'] . '.'][$this->data['databaseRow']['CType'][0]];
}
if($tsConfigValue) {
// load TS parser
$parser->parse($tsConfigValue);
$areas = $parser->setup;
if(count($areas) != (int)$this->areaCount) {
$areasDoNotMatchAreaCount = true;
}
}
}
...
}
}
Da TYPO3 das Vorausfüllen von Feldern basierend auf dem CType nicht ermöglicht (zumindest wüsste ich nichts davon), habe ich hier einfach den CType als Key übergeben und man kann somit für verschiedenen CTypes Voreinstellungen setzen, hier z.B. für ein Grid mit 12 Spalten und einem Bereich, wobei dieser in Spalte 3 anfängt und eine Breite von 6 Spalten hat:
TCAdefaults.tt_content.pi_flexform.data.sDEF.grid.myFirstContentElement (
c1 {
column {
start = 3
end = 9
span = 6
}
row {
start = 1
end = 2
span = 1
}
}
)
TCAdefaults.tt_content.grid.mySecondContentElement < TCAdefaults.tt_content.pi_flexform.data.sDEF.grid.myFirstContentElement
Einbinden der Javascript-Libraries
Ich habe dazu eine, meiner Ansicht nach, sehr schöne Javascript Implementierung von Anthony Dugois auf Codepen gefunden. Die Anwendung (inkl. Libraries) sind im bereitgestellten Archiv enthalten. Einfach downloaden und die Namespaces anpassen (TYPO3/CMS/Gridexample/grideditor/). Alle Javascript Dateien sollten in eurem Extension Verzeichnis unter Resources/Public/JavaScript/grideditor landen.
Anpassen der Fluid-Templates
Um auf die Grid-Konfiguration im Fluid-Template einfachen Zugriff zu haben, habe ich einen ViewHelper erstellt. Dieser parsed das übergebene TypoScript und gibt ein Array zurück:
<?php
namespace Varioous\Gridexample\ViewHelpers;
use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
class TypoScriptParserViewHelper extends AbstractViewHelper
{
...
public function render()
{
$parser = GeneralUtility::makeInstance(TypoScriptParser::class);
$parser->parse($this->arguments['typoscript']);
return $this->removeDots($parser->setup);
}
...
}
?>
Im Fluid-Template holen wir uns die Konfiguration und rendern jeden Bereich über eine for each Schleife. In unserem Beispiel einfach über den vhs Viewhelper FluidTYPO3\Vhs\ViewHelpers\Content\Render:
...
<f:variable name="gridConfig" value="{gridexample:typoScriptParser(typoscript: '{grid}')}" />
<f:for each="{gridConfig.grid.areas}" as="area" key="areaKey" iteration="iterator">
<f:variable name="areaWidth" value="{area.column.span}" />
<f:variable name="areaOffset" value="{area.column.start - 1}" />
<f:if condition="{prevArea} && {prevArea.row.start} == {area.row.start}">
<f:variable name="areaOffset" value="{areaOffset - prevArea.column.span - prevArea.column.start + 1}" />
</f:if>
<div class="col-{areaWidth} col-offset-{areaOffset}">
<f:if condition="{data.tx_gridelements_view_raw_columns.{iterator.index}}">
<f:variable name="contentElements" value="{data.tx_gridelements_view_raw_columns.{iterator.index}}" />
<f:for each="{contentElements}" as="contentElement">
<v:content.render contentUids="{0: contentElement.uid}" />
</f:for>
</f:if>
</div>
<f:variable name="prevArea" value="{area}" />
</f:for>
...
Das war's auch schon wieder! Ich hoffe, ihr könnt damit was anfangen.
Download
Hier könnt ihr euch alle benötigten Dateien einfach als ZIP-Archiv downloaden.
Solltet ihr Ideen, Verbesserungsvorschläge oder Kritik oder ich irgendwo einen absoluten Blödsinn fabriziert haben, einfach unter georg(at)varioous.at melden.
Wir entwickeln digitale Lösungen mit Leidenschaft
Warum wir das tun? Weil die Verwirklichung Ihrer Vision unser größter Anspruch und die schönste Anerkennung ist. Deshalb nehmen wir uns gerne ausreichend Zeit für die Realisierung Ihres digitalen Projekts.
Kontaktieren Sie uns, wir sind gerne für Ihre Fragen da: