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">
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
@Erik: Danke für den Hinweis – ist geändert!
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… 😉
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