Grideditor für TYPO3
Inhaltselemente

TYPO3 bietet momentan noch keine Core-Funktionalität, um ein Grid-System im Backend anlegen zu können. Die Erweiterung gridelements schafft hier Abhilfe. Da wir aber eine möglichst große Flexibilität bei der Spaltenaufteilung haben wollten, habe ich einen einfachen Grideditor als neuen renderType erstellt.

TYPO3 Grid Editor

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.

<?phpnamespace 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:

<?phpnamespace 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.

Download

Solltet ihr Ideen, Verbesserungsvorschläge oder Kritik oder ich irgendwo einen absoluten Blödsinn fabriziert haben, einfach unter georg(at)varioous.at melden.