Typo3 tx_form: Eigenes Formularelement erstellen / custom form element

Versuch einer Anleitung für Dummies

(oder Normalsterbliche, die einfach nur einen Job zu erledigen haben)

Vor einigen Tagen haben wir einen Beitrag veröffentlicht, wie man ein die Typo3 Core Extension tx_form um ein eigenes Formularelement erweitern kann.

Die Typo3 Extension tx_form ist zwei Dingen vorbildlich: Der modularen und konsequent objektorientierten Architektur. Und im bisher unerreichten Verwirrungsfaktor für Normalsterbliche. Ich weiß nicht, wie viele Stunden wir im Ordner sysext/form verbracht haben, um zu verstehen, welches mixin von welchem übergeordneten mixin per __inheritances welche Konfigurations-Häppchen erbt.

Genauso unverständlich und eine echte Motivationsbremse ist es für viele Typo3-Entwicklern, dass tx_form auf eine weitere Konfigurationssprache setzt. TypoScript, TCA und FlexForm-XML? Alles alte Hasen, da geht noch einer. Also jetzt noch YAML dazu. Zwei spaces statt tab. Unlesbare Dateien und kaum brauchbare Editoren. Und was die Erweiterbarkeit angeht noch ziemlich buggy. Na dann, lasst uns mal einsteigen.

1. Entscheide Dich für einen Namen

Jedes Formular-Element braucht einen eindeutige Bezeichner / Namen. Entscheide Dich jetzt und notiere den Namen für Dein Formular-Element. Vielleicht möchtest Du ein Formular-Element, mit dem man eine Sternebewertung im Frontend machen kann. Dann nenne Dein Formularelement „Sterne„. Oder man soll ein Emoji wählen können. Dann nenne es „Emoji„.

Nenne es nicht „Checkbox„, „Text„, „DatePicker“ oder irgendetwas anderes, was es vielleicht schon geben könnte.

In diesem Beispiel werden wir es Emoji nennen. Immer, wenn Du ein gelbes Emoji im Quelltext oder Dateinamen siehst, ersetzt Du es mit Deinem eigenen Bezeichner.

Wenn Du die beiden Felder hier ausfüllst, wird im Quelltext alles automatisch für Dich ersetzt:



2. Eine Extension anlegen

(oder eine vorhandene nutzen)

Wir ignorieren mal alle Normen und halten es simpel. Folgende Dateien und Struktur brauchst Du in Deiner Extension.

Ersetze extname durch den Ordner-Namen Deiner Extension, in der Du arbeitest.
Ersetze das Wort Emoji im Dateinamen durch Deinen Bezeichner.

extname
│
├── Configuration
│   ├── TypoScript
│   │   └── setup.txt
│   └── Yaml
│       └── FormSetup.yaml
├── Resources
│   ├── Private
│   │   └── FormFramework
│   │       └── Partials
│   │           └── Emoji.html
│   └── Public
│       ├── Icons
│       │   └── Emoji-icon.svg
│       └── JavaScript
│           └── Backend
│               └── FormEditor
│                   └── EmojiViewModel.js
├── ext_emconf.php
├── ext_localconf.php
└── ext_typoscript_setup.txt

3. Inhalte der Dateien

Folgender Code muss in die einzelnen Dateien:


Configuration/TypoScript/setup.txt

Die Zahl 1534503706 durch den aktuellen Timestamp ersetzen. Den Extension-Namen extname durch den Namen des Extension-Ordners ersetzen.


# Einstellungen für das Frontend
plugin.tx_form.settings.yamlConfigurations {
   1534503706 = EXT:extname/Configuration/Yaml/FormSetup.yaml
}
# Einstellungen für das Backend
module.tx_form.settings.yamlConfigurations {
   1534503706 = EXT:extname/Configuration/Yaml/FormSetup.yaml
}

Configuration/Yaml/FormSetup.yaml

Das Emoji im Code durch Deinen Bezeichner ersetzen (s. Schritt 1).
Den Extension-Namen Extname durch den Namen des Extension-Ordners ersetzen, aber mit großem Anfangsbuchstaben bzw. upperCamelCase-Schreibweise!.
Den Extension-Namen extname durch den Namen des Extension-Ordners ersetzen, aber mit kleinem Anfangsbuchstaben!.
Wenn Du mehrere Extension hast, die Formularfelder einfügen, dann muss die 125 im Quelltext eindeutig sein. Auch hier könntest Du einfach den aktuellen Timestamp verwenden.


TYPO3:
  CMS:
    Form:
      prototypes:
        standard:

          # -----------------------------------------------------------------------------------
          # Darstellung des Formularelementes im Backend:
          # In der Übersicht / Listenansicht, mittlere Spalte

          formEditor:

            # Für Darstellung im Backend: JS-Dateien/Module registrieren, die per requireJS
            # eingebunden werden. tx_form sucht in genau diesem Pfad nach den JS-Dateien:
            # 'EXT:..../Resources/Public/JavaScript/Backend/FormEditor/......js 
            dynamicRequireJsModules:
              additionalViewModelModules:
                - 'TYPO3/CMS/Extname/Backend/FormEditor/EmojiViewmodel'
            
            # Für Darstellung im Backend: Welches Fluid-Template verwenden?
            # Hier einfach das Standard-Template von tx_core
            formEditorPartials:
              FormElement-Emoji: 'Stage/SimpleTemplate'

          
          formElementsDefinition:

            # ---------------------------------------------------------
            # Für Ausgabe im Frontend: Zusätzliche Pfade, die für Rendern des Feldes
            # durchsucht werden sollen. Der numerische Key muss eindeutig sein 

            Form:
              renderingOptions:
                templateRootPaths:
                  125: 'EXT:extname/Resources/Private/FormFramework/Templates/'
                partialRootPaths:
                  125: 'EXT:extname/Resources/Private/FormFramework/Partials/'
                layoutRootPaths:
                  125: 'EXT:extname/Resources/Private/FormFramework/Layouts/'

            # ---------------------------------------------------------
            # Darstellung der Formularfelder im Backend
            # Detailansicht mit Formularfeldern in der rechten Spalte
                  
            Emoji:
            
              # Erbt die Standard-Darstellung und Felder, z.B. "Label" und "Description"
              # siehe SYSEXT:form/Configuration/Yaml/FormEditorSetup.yaml
              __inheritances:
                10: 'TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin'

              # Überschrift (erscheint als Titel in Übersicht und über dem Formular)
              formEditor:
                label: 'Dein Formularelement'
                group: custom
                groupSorting: 1010

                # Dieses Icon wird in der ext_localconf.php registriert
                iconIdentifier: 'Emoji-icon'
                
                # Defaults für die Formularfelder
                predefinedDefaults:
                  properties:
                    test: ''

                # Hier können eigene Formularfelder definiert werden
                editors:
                  # 200 wurde von FormElementMixin geerbt und enhält das Feld "Label"
                  # 230 wurde von auch geerbt und enhält das Feld "Description"
                  # 700 enthält die Angaben für Spaltenbreiten xs, sm, ... beim Einfügen in ein Row-Element
                  # 800 ist die required-Checkbox (Pflichtfeld)
                  300:
                    identifier: 'test'
                    templateName: 'Inspector-TextEditor'
                    label: 'Test'
                    propertyPath: 'properties.test'

Resources/Private/FormFramework/Partials/Emoji.html

Das eigentliche Fluid-Template für die Ausgabe im Frontend.

<div>
	<h2>{element.properties.elementDescription}</h2>
	<f:form.textfield
		property="{element.identifier}"
		id="{element.uniqueIdentifier}"
		class="{element.properties.elementClassAttribute} form-control form-field"
		errorClass="{element.properties.elementErrorClassAttribute}"
		additionalAttributes="{formvh:translateElementProperty(element:element, property: 'fluidAdditionalAttributes')}"
	/>	
</div>

Resources/Public/Icons/Emoji-icon.svg

Ein simples, quadratisches SVG-Icon für die Darstellung im Backend.


Resources/Public/JavaScript/Backend/FormEditor/EmojiViewmodel.js


// Resources/Public/JavaScript/Backend/FormEditor/EmojiViewmodel.js

define([
    'jquery',
    'TYPO3/CMS/Form/Backend/FormEditor/Helper'
], function ($, Helper) {
    'use strict';

    return (function ($, Helper) {

        /**
         * @private
         *
         * @var object
         */
        var _formEditorApp = null;

        /**
         * @private
         *
         * @return object
         */
        function getFormEditorApp() {
            return _formEditorApp;
        };

        /**
         * @private
         *
         * @return object
         */
        function getPublisherSubscriber() {
            return getFormEditorApp().getPublisherSubscriber();
        };

        /**
         * @private
         *
         * @return object
         */
        function getUtility() {
            return getFormEditorApp().getUtility();
        };

        /**
         * @private
         *
         * @param object
         * @return object
         */
        function getHelper() {
            return Helper;
        };

        /**
         * @private
         *
         * @return object
         */
        function getCurrentlySelectedFormElement() {
            return getFormEditorApp().getCurrentlySelectedFormElement();
        };

        /**
         * @private
         *
         * @param mixed test
         * @param string message
         * @param int messageCode
         * @return void
         */
        function assert(test, message, messageCode) {
            return getFormEditorApp().assert(test, message, messageCode);
        };

        /**
         * @private
         *
         * @return void
         * @throws 1491643380
         */
        function _helperSetup() {
            assert('function' === $.type(Helper.bootstrap),
                'The view model helper does not implement the method "bootstrap"',
                1491643380
            );
            Helper.bootstrap(getFormEditorApp());
        };

        /**
         * @private
         *
         * @return void
         */
        function _subscribeEvents() {
            /**
             * @private
             *
             * @param string
             * @param array
             *              args[0] = formElement
             *              args[1] = template
             * @return void
             */
            getPublisherSubscriber().subscribe('view/stage/abstract/render/template/perform', function (topic, args) {
                if (args[0].get('type') === 'Emoji') {
                    getFormEditorApp().getViewModel().getStage().renderSimpleTemplateWithValidators(args[0], args[1]);
                }
            });
        };

        /**
         * @public
         *
         * @param object formEditorApp
         * @return void
         */
        function bootstrap(formEditorApp) {
            _formEditorApp = formEditorApp;
            _helperSetup();
            _subscribeEvents();
        };

        /**
         * Publish the public methods.
         * Implements the "Revealing Module Pattern".
         */
        return {
            bootstrap: bootstrap
        };
    })($, Helper);
});

ext_emconf.php

Der übliche Code, um eine Extension zu registrieren – falls nicht schon vorhanden.

<?php
$EM_CONF[$_EXTKEY] = [
    'title' => 'Meine Extension',
    'description' => '',
    'category' => 'plugin',
    'author' => 'David Bascom',
    'author_email' => 'deine@email.de',
    'state' => 'stable',
    'internal' => '',
    'uploadfolder' => '1',
    'createDirs' => '',
    'clearCacheOnLoad' => 0,
    'version' => '0.1',
    'constraints' => [
        'depends' => [
            'typo3' => '8.7.0',
        ],
        'conflicts' => [],
        'suggests' => [],
    ],
];

ext_localconf.php

Registriert das Icon

<?php 
if (TYPO3_MODE === 'BE') {
    /** @var \TYPO3\CMS\Core\Imaging\IconRegistry $iconRegistry */
    $iconRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Imaging\IconRegistry::class);
    $iconRegistry->registerIcon(
        't3-form-Emoji-icon',
        \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
        ['source' => 'EXT:extname/Resources/Public/Icons/Emoji-icon.svg']
    );
}

ext_typoscript_setup.txt

Lädt das TypoScript, ohne das im Root-Template die „Enthält Erweiterungen“ gewählt werden müssen.

<INCLUDE_TYPOSCRIPT: source="FILE:EXT:extname/Configuration/Typoscript/setup.txt">

One thought to “Typo3 tx_form: Eigenes Formularelement erstellen / custom form element”

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.