If you like to add a custom form element, please follow these steps. For detailed information and documentation of ext:form please visit the official documentation: https://docs.typo3.org/typo3cms/extensions/form/Index.html
First of all create a new extension, which will hold all the configuration, templates, etc.
Let's call it your_custom_extension
whithin this how to.
This is an example structure of the extension:
├── Classes
├── Configuration
│ ├── TypoScript
│ │ ├── constants.txt
│ │ └── setup.txt
│ └── Yaml
│ ├── BaseSetup.yaml
│ └── FormEditorSetup.yaml
├── Documentation
├── Resources
│ ├── Private
│ │ ├── Frontend
│ │ │ └── Partials
│ │ │ └── YourCustomElement.html
│ │ └── Language
│ │ └── Backend.xlf
│ └── Public
│ ├── Icons
│ │ ├── Extension.svg
│ │ └── your_custom_element_icon.svg
│ └── JavaScript
│ └── Backend
│ └── FormEditor
│ └── YourCustomElementViewModel.js
├── Tests
├── composer.json
├── ext_emconf.php
├── ext_localconf.php
└── ext_typoscript_setup.txt
Ok, let's get our hands dirty.
As the configuration of the ext:form is written in YAML, you have to define your new shiny element here.
# Configuration/Yaml/BaseSetup.yaml
TYPO3:
CMS:
Form:
prototypes:
standard:
formEditor:
formEditorPartials:
FormElement-YourCustomElement: 'Stage/SimpleTemplate'
formElementsDefinition:
Form:
renderingOptions:
templateRootPaths:
100: 'EXT:your_custom_extension/Resources/Private/Frontend/Templates/'
partialRootPaths:
100: 'EXT: your_custom_extension/Resources/Private/Frontend/Partials/'
layoutRootPaths:
100: 'EXT: your_custom_extension/Resources/Private/Frontend/Layouts/'
YourCustomElement:
__inheritances:
10: 'TYPO3.CMS.Form.mixins.formElementMixins.YourCustomElementMixin'
mixins:
formElementMixins:
YourCustomElementMixin:
__inheritances:
10: 'TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin'
At this point you will give your element a template that should be used in the Stage area of the form editor FormElement-YourCustomElement
. For more information please read: https://docs.typo3.org/typo3cms/extensions/form/ApiReference/Index.html#stage-simpletemplate
In most cases the Stage/SimpleTemplate
will be enough.
At renderingOptions
we define our custom path to templates, layouts and / or partials, that should be used to render within the website (Frontend).
Let's register our custom new element underneath formElementsDefinition
with the name of the element. This one should be unique, as it could be replaced / used by another extension otherwise. In this example we create a custom mixin for that element, which inherites from the TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin
which I discovered is the most basic mixin you would like to use in combination with the Stage/SimpleTemplate
. Other mixins could be used as well. If you base on an input field for example, you could use the TYPO3.CMS.Form.mixins.formElementMixins.TextMixin
provided by ext:form.
Now that we have defined the basics for the frontend, let's define the configuration for the form editor:
# Configuration/Yaml/FormEditorSetup.yaml
TYPO3:
CMS:
Form:
prototypes:
standard:
formEditor:
dynamicRequireJsModules:
additionalViewModelModules:
1588750194: 'TYPO3/CMS/YourCustomExtension/Backend/FormEditor/YourCustomElementViewModel'
formEditorPartials:
FormElement-YourCustomElement: 'Stage/SimpleTemplate'
formElementsDefinition:
YourCustomElement:
formEditor:
label: 'formEditor.elements.YourCustomElement.label'
group: custom
groupSorting: 1000
iconIdentifier: 't3-form-icon-your-custom-element'
mixins:
formElementMixins:
YourCustomElementMixin:
__inheritances:
10: 'TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin'
Notice: additionalViewModelModules should be registered with a number in YAML. Otherwise the config will be overridden by the next extension, that registers a ViewModel.
As the configuration could be different to the basic configuration for the form editor, I configured some settings here again.
Let's keep an eye on the new and elementary parts.
Register your element for the "create new element" modal with this snippet:
formElementsDefinition:
YourCustomElement:
formEditor:
label: 'formEditor.elements.YourCustomElement.label'
group: custom
groupSorting: 1000
iconIdentifier: 't3-form-icon-your-custom-element'
- The
key
used atlabel
is the key used in the language file group
andgroupSorting
should be self explaning- The icon used at
iconIdentifier
as to be registered with theIconFactory
and should be a SVG
The most trickiest part for me to figure out was to define a model for the stage area as the other stuff works with this little configuration. Thus, here the snippet to get the full experience of the element used in the stage (label of the element, used validator information, etc.):
formEditor:
dynamicRequireJsModules:
additionalViewModelModules:
1588750194: 'TYPO3/CMS/YourCustomExtension/Backend/FormEditor/YourCustomElementViewModel'
Notice: additionalViewModelModules should be registered with a number in YAML. Otherwise the config will be overridden by the next extension, that registers a ViewModel.
This one will point to a JavaScript model (defined under JavaScript) within your extension, that will hold all information and will trigger the rendering within the stage.
This was a lot of YAML until now!
Let's register it thus the ext:form will notice it.
My suggestions is to use the ext_typoscript_setup.txt
at the root level of your extension.
# ext_typoscript_setup.txt
module.tx_form {
settings {
yamlConfigurations {
40 = EXT:your_custom_extension/Configuration/Yaml/BaseSetup.yaml
50 = EXT:your_custom_extension/Configuration/Yaml/FormEditorSetup.yaml
}
}
view {
partialRootPaths {
40 = EXT:your_custom_extension/Resources/Private/Backend/Partials/
}
}
}
This will help for the backend, but the frontend needs some definition as well, thus we have to use the common Configuration/TypoScript/setup.txt
for that:
# Configuration/TypoScript/setup.txt
plugin.tx_form {
settings {
yamlConfigurations.40 = EXT:your_custom_extension/Configuration/Yaml/BaseSetup.yaml
}
view {
partialRootPaths.100 = EXT:your_custom_extension/Resources/Private/Frontend/Partials/
}
}
That's it for TypoScript
To override the language file for the backend, we have to override that one within ext_localconf
:
# ext_localconf.php
// register language file for backend
$GLOBALS['TYPO3_CONF_VARS']['SYS']['locallangXMLOverride']['EXT:form/Resources/Private/Language/Database.xlf'][] = 'EXT:your_custom_extension/Resources/Private/Language/Backend.xlf'
Here should the label etc. be placed as mentioned in the first section.
As we configured everything in the first step, we could use fluid layouts, templates and partials for rendering in the Frontend. Have a look at ext:form how to use them.
This snippet is mandatory to display your custom element correctly with all features in the stage area if you use the Stage/SimpleElement
template for your element defined in the first step:
// Resources/Public/JavaScript/Backend/FormEditor/YourCustomElementViewModel.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') === 'YourCustomElement') {
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);
});
You have only one thing to change:
if (args[0].get('type') === 'YourCustomElement') { // use your registered unique element name here
getFormEditorApp().getViewModel().getStage().renderSimpleTemplateWithValidators(args[0], args[1]);
}
@reneroboter I had a look at your fork and adapted the change. Thanks for letting me know.