Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save manuelselbach/dc63abd313694c594d480b163a5f3053 to your computer and use it in GitHub Desktop.
Save manuelselbach/dc63abd313694c594d480b163a5f3053 to your computer and use it in GitHub Desktop.
How to add a custom element for the formEditor of ext:form

EXT:form How to add a custom form element

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

Basics

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.

YAML

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 at label is the key used in the language file
  • group and groupSorting should be self explaning
  • The icon used at iconIdentifier as to be registered with the IconFactory 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.

TypoScript

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

Language file

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.

Layout / Template / Partial

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.

JavaScript

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]);
}
@manuelselbach
Copy link
Author

Hi @VivekNitsan,

since I’ve written this instruction a lot has changed in TYPO3. May I ask you to get in contact via Slack and the channel #ext-form? If it’s possible, it also might help to see the complete code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment