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:
                125: '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(
        '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">

4 Gedanken zu „Typo3 tx_form: Eigenes Formularelement erstellen / custom form element“

  1. Hallo Leute,
    erst mal vielen herzlichen Dank für dieses tolle Tool! Das ist Spitze und sehr hilfreich!
    Mir ist jetzt noch ein Fehler aufgefallen:
    Der „iconIdentifier: ‚Xxxxxx-icon'“ aus der YAML Datei muss mit dem Eintrag in der ext_localconf.php übereinstimmen.
    Es müsste also die Zeile mit „t3-form-Xxxxxx-icon“ in „Xxxxxx-icon“ getauscht werden.

    Grüße…
    Erik

  2. Die Einleitung kann ich nur bestätigen, selbst wenn man ein ambitionierter Anwender ist, der keine Probleme hat, T3 aufzusetzen und zu konfigureren etc., bei Form stellen sich neue Herausforderungen, die sich nicht wirklich auf Anhieb durch die Doku lösen lassen oder sich nur sehr versteckt finden.

    Die Beschreibung oben – hervorragend!

    Da gehts dann irgendwann nach dem Prinzip „Try & Error“.
    Beispiel? Ein Text-Inputfeld -kann- ja auch mit einer sog. Datalist arbeiten. Das hat gewisse Vorteile gegenüber einer Selectlist, vor allem, wenn die Daten einen gewissen Rahmen sprengen oder gar dynamisch erzeugt werden müssen.

    Nur wie bringt man das diesem Feld bei, auf die Datalist zuzugreifen?
    In diversen Foren, der Doku und auf FB – tagelang – nach Lösungen, Hinweisen gesucht, da war von einer eigenen EXT bis eigenes Form-Element (sauberste Lösung) bis geht nicht (…gibt es aber nicht…) so gut wie alles dabei.
    Task: in den html-Tag des Feldes muss „nur“: list=“12345″ eingetragen werden – entspricht der ID der Datalist.

    Kleiner Tipp: man baue in ein Textfeld einen Placeholder ein und guckt im Quelltext was passiert… 😉
    In meinem Fall bin ich dann ins YAML und hab direkt unter der Placeholderzeile – meine Zeile eingetragen.
    Gespeichert, getestet, funktionert.
    Da man das YAML ja auch per TypoScript manipulieren kann, bekommt man auf die Weise auch ohne Ext oder eigene Elemente den Tag ergänzt.

    In meinem Fall war das eine auf den ersten Blick eigentlich ziemlich einfache Aufgabenstellung, jeder Teil für sich (Liste, Formular) kein Thema. Nur den Knoten in TYPO3 resp. Form hinzubekommen…hätte ich noch Haare, würden jetzt einige fehlen… 😉

  3. Vielen Dank für dieses SEHR hilfreiche Tool. Die Art und Weise mittels eigener Eingabe Namen und Bezeichner zu vergeben und diese in der Folge einzublenden ist klasse zum Verständnis der Bestandteile 🙂
    Könnt ihr mir sagen, ob auch dynamische Elemente, die TYPOscript nutzen, mit dem neuen Formularelement verknüpft werden können, als cobject z.B.? Meine Aufgabe ist es, ein Datumsfeld in einem Formular für das Geburtsdatum auszuwerten und in Abhängigkeit vom Alter des/der Benutzer*in eine Seite im Formular einzublenden oder nicht. Bisher habe ich da leider nichts brauchbares gefunden, vielleicht habt ihr ja einen Tipp?!
    Vielen Dank im voraus
    Andreas

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.